Skip to content

Commit c90b946

Browse files
committed
split into smaller source files
1 parent f55e344 commit c90b946

9 files changed

+769
-743
lines changed

EventHandler.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package org.encalmo.lambda
2+
3+
trait EventHandler {
4+
5+
/** Abstract lambda invocation handler method. Provide your business logic here.
6+
*/
7+
def handleRequest(input: String)(using LambdaContext): String
8+
}

EventHandlerTag.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package org.encalmo.lambda
2+
3+
trait EventHandlerTag {
4+
5+
/** Event tag will printed in the beginning of the log. Override to mark each log with event-specific tag. Default to
6+
* None.
7+
*/
8+
def getEventHandlerTag(event: String): Option[String] = None
9+
}

LambdaContext.scala

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package org.encalmo.lambda
2+
3+
import com.amazonaws.services.lambda.runtime.ClientContext
4+
import com.amazonaws.services.lambda.runtime.CognitoIdentity
5+
import com.amazonaws.services.lambda.runtime.Context
6+
import com.amazonaws.services.lambda.runtime.LambdaLogger
7+
import com.amazonaws.services.lambda.runtime.logging.LogLevel
8+
9+
import scala.io.AnsiColor
10+
import scala.jdk.OptionConverters.*
11+
12+
/** Lambda invocation context data. */
13+
final case class LambdaContext(
14+
requestId: String,
15+
headers: Map[String, String],
16+
lambdaEnvironment: LambdaEnvironment,
17+
switchOffDebugMode: Function0[Unit]
18+
) extends Context {
19+
20+
export LambdaContext.Logger.{info, debug, error, trace}
21+
22+
def addProperties(
23+
properties: Map[String, String]
24+
): LambdaContext =
25+
copy(lambdaEnvironment = lambdaEnvironment.withAdditionalVariables(properties))
26+
27+
export lambdaEnvironment.{
28+
getFunctionName,
29+
getFunctionVersion,
30+
getLogGroupName,
31+
getLogStreamName,
32+
getMemoryLimitInMB,
33+
getLogger
34+
}
35+
36+
final def maybeGetProperty(key: String): Option[String] =
37+
lambdaEnvironment
38+
.maybeGetProperty(key)
39+
.orElse(headers.get(key))
40+
41+
final def getProperty(key: String): String =
42+
maybeGetProperty(key)
43+
.getOrElse(
44+
throw new Exception(s"Lambda invocation property [$key] not found.")
45+
)
46+
47+
override def getAwsRequestId(): String = requestId
48+
49+
override def getRemainingTimeInMillis(): Int =
50+
getProperty("Lambda-Runtime-Deadline-Ms").toInt
51+
52+
override def getInvokedFunctionArn(): String =
53+
getProperty("Lambda-Runtime-Invoked-Function-Arn")
54+
55+
override def getIdentity(): CognitoIdentity =
56+
throw new UnsupportedOperationException()
57+
58+
override def getClientContext(): ClientContext =
59+
throw new UnsupportedOperationException()
60+
}
61+
62+
object LambdaContext {
63+
64+
/** Returns AWS Region of the current lambda instance. */
65+
object Logger {
66+
67+
inline def info(
68+
message: String
69+
)(using lambdaContext: LambdaContext): Unit =
70+
lambdaContext
71+
.getLogger()
72+
.log(
73+
s"[${lambdaContext.getFunctionName()}] $message",
74+
LogLevel.INFO
75+
)
76+
77+
inline def debug(
78+
message: String
79+
)(using lambdaContext: LambdaContext): Unit =
80+
if (lambdaContext.lambdaEnvironment.isDebugMode)
81+
then
82+
lambdaContext
83+
.getLogger()
84+
.log(
85+
s"${AnsiColor.BLUE}[${lambdaContext.getFunctionName()}] $message${AnsiColor.RESET}",
86+
LogLevel.DEBUG
87+
)
88+
else ()
89+
90+
inline def trace(
91+
message: String
92+
)(using lambdaContext: LambdaContext): Unit =
93+
if (lambdaContext.lambdaEnvironment.isTraceMode)
94+
then
95+
lambdaContext
96+
.getLogger()
97+
.log(
98+
s"${AnsiColor.BLUE}[${lambdaContext.getFunctionName()}] $message${AnsiColor.RESET}",
99+
LogLevel.TRACE
100+
)
101+
else ()
102+
103+
inline def error(
104+
message: String
105+
)(using lambdaContext: LambdaContext): Unit =
106+
lambdaContext
107+
.getLogger()
108+
.log(
109+
s"${AnsiColor.RED_B}${AnsiColor.WHITE}[${lambdaContext
110+
.getFunctionName()}][ERROR] $message${AnsiColor.RESET}",
111+
LogLevel.ERROR
112+
)
113+
}
114+
}

LambdaEnvironment.scala

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
package org.encalmo.lambda
2+
3+
import com.amazonaws.services.lambda.runtime.LambdaLogger
4+
import com.amazonaws.services.lambda.runtime.logging.LogLevel
5+
6+
import java.io.PrintStream
7+
import java.net.URI
8+
import java.net.http.*
9+
import java.net.http.HttpRequest.BodyPublishers
10+
import scala.collection.mutable
11+
import scala.io.AnsiColor
12+
import scala.jdk.CollectionConverters.*
13+
14+
/** Provides access to the lambda instance environment. */
15+
final class LambdaEnvironment(
16+
variablesOverrides: Map[String, String] = Map.empty,
17+
retrieveSecrets: (String => Option[String]) => Map[String, String] = _ => Map.empty
18+
) {
19+
20+
export LambdaEnvironment.Logger.{info, debug, error, trace}
21+
22+
def withAdditionalVariables(
23+
additionalVariablesOverrides: Map[String, String]
24+
): LambdaEnvironment = new LambdaEnvironment(
25+
variablesOverrides ++ additionalVariablesOverrides
26+
)
27+
28+
inline val LAMBDA_VERSION_DATE = "2018-06-01"
29+
30+
private val systemVariables: mutable.Map[String, String] =
31+
System.getenv().asScala
32+
33+
private val systemProperties: mutable.Map[String, String] =
34+
System.getProperties().asScala
35+
36+
private val secrets = retrieveSecrets(key =>
37+
variablesOverrides
38+
.get(key)
39+
.orElse(systemProperties.get(key))
40+
.orElse(systemVariables.get(key))
41+
)
42+
43+
private def getRuntimeApi(): String =
44+
getProperty("AWS_LAMBDA_RUNTIME_API")
45+
46+
final def getFunctionName(): String =
47+
getProperty("AWS_LAMBDA_FUNCTION_NAME")
48+
49+
final def getFunctionVersion(): String =
50+
getProperty("AWS_LAMBDA_FUNCTION_VERSION")
51+
52+
final def getMemoryLimitInMB(): Int =
53+
getProperty("AWS_LAMBDA_FUNCTION_MEMORY_SIZE").toInt
54+
55+
final def getLogGroupName(): String =
56+
getProperty("AWS_LAMBDA_LOG_GROUP_NAME")
57+
58+
final def getLogStreamName(): String =
59+
getProperty("AWS_LAMBDA_LOG_STREAM_NAME")
60+
61+
final val isDebugMode: Boolean =
62+
maybeGetProperty("LAMBDA_RUNTIME_DEBUG_MODE")
63+
.map(parseBooleanFlagDefaultOff)
64+
.getOrElse(false)
65+
66+
final val isTraceMode: Boolean =
67+
maybeGetProperty("LAMBDA_RUNTIME_TRACE_MODE")
68+
.map(parseBooleanFlagDefaultOff)
69+
.getOrElse(false)
70+
71+
final val shouldDisplayAnsiColors: Boolean =
72+
maybeGetProperty("ANSI_COLORS_MODE")
73+
.map(parseBooleanFlagDefaultOn)
74+
.getOrElse(maybeGetProperty("NO_COLOR").forall(p => p.trim() != "1"))
75+
76+
final val shouldLogStructuredJson: Boolean =
77+
!shouldDisplayAnsiColors &&
78+
maybeGetProperty("LAMBDA_RUNTIME_LOG_TYPE")
79+
.map(_.toUpperCase().contains("STRUCTURED"))
80+
.getOrElse(true)
81+
82+
final val shouldLogInJsonArrayFormat: Boolean =
83+
maybeGetProperty("LAMBDA_RUNTIME_LOG_FORMAT")
84+
.map(_.toUpperCase().contains("JSON_ARRAY"))
85+
.getOrElse(false)
86+
87+
final val shouldLogInJsonStringFormat: Boolean =
88+
maybeGetProperty("LAMBDA_RUNTIME_LOG_FORMAT")
89+
.map(_.toUpperCase().contains("JSON_STRING"))
90+
.getOrElse(false)
91+
92+
final val shouldLogResponseIncludeRequest: Boolean =
93+
!shouldDisplayAnsiColors &&
94+
maybeGetProperty("LAMBDA_RUNTIME_LOG_RESPONSE_INCLUDE_REQUEST")
95+
.map(parseBooleanFlagDefaultOn)
96+
.getOrElse(true)
97+
98+
final def maybeGetProperty(key: String): Option[String] =
99+
variablesOverrides
100+
.get(key)
101+
.orElse(systemProperties.get(key))
102+
.orElse(systemVariables.get(key))
103+
.orElse(secrets.get(key))
104+
105+
final def getProperty(key: String): String =
106+
maybeGetProperty(key)
107+
.getOrElse(
108+
throw new Exception(
109+
s"Lambda environment property [$key] not found."
110+
)
111+
)
112+
113+
final def getLogger(): LambdaLogger = SystemOutLambdaLogger
114+
115+
lazy val lambdaOut: PrintStream =
116+
if (shouldDisplayAnsiColors)
117+
then LambdaEnvironment.originalOut
118+
else NoAnsiColors.getPrintStream(LambdaEnvironment.originalOut)
119+
120+
final def setCustomOut(): Unit =
121+
System.setOut(lambdaOut)
122+
System.setErr(lambdaOut)
123+
124+
final def resetOut(): Unit =
125+
System.setOut(LambdaEnvironment.originalOut)
126+
System.setErr(LambdaEnvironment.originalOut)
127+
128+
final val nextEventUrl: URI =
129+
URI.create(
130+
s"http://${getRuntimeApi()}/$LAMBDA_VERSION_DATE/runtime/invocation/next"
131+
)
132+
133+
final val nextEventRequest: HttpRequest =
134+
HttpRequest.newBuilder(nextEventUrl).GET().build()
135+
136+
final val initErrorUrl: URI =
137+
URI.create(
138+
s"http://${getRuntimeApi()}/$LAMBDA_VERSION_DATE/runtime/init/error"
139+
)
140+
141+
final def responseUrl(requestId: String): URI =
142+
URI.create(
143+
s"http://${getRuntimeApi()}/$LAMBDA_VERSION_DATE/runtime/invocation/$requestId/response"
144+
)
145+
146+
final def responseRequest(requestId: String, output: String): HttpRequest =
147+
HttpRequest
148+
.newBuilder(responseUrl(requestId))
149+
.POST(BodyPublishers.ofString(output))
150+
.build()
151+
152+
final def errorUrl(requestId: String): URI =
153+
URI.create(
154+
s"http://${getRuntimeApi()}/$LAMBDA_VERSION_DATE/runtime/invocation/$requestId/error"
155+
)
156+
157+
final inline def parseBooleanFlagDefaultOff: String => Boolean = s =>
158+
s.toLowerCase() match {
159+
case "true" => true
160+
case "on" => true
161+
case _ => false
162+
}
163+
164+
final inline def parseBooleanFlagDefaultOn: String => Boolean = s =>
165+
s.toLowerCase() match {
166+
case "false" => false
167+
case "off" => false
168+
case _ => true
169+
}
170+
}
171+
172+
object LambdaEnvironment {
173+
174+
val originalOut: PrintStream = System.out
175+
object Logger {
176+
177+
inline def info(
178+
message: String
179+
)(using lambdaEnvironment: LambdaEnvironment): Unit =
180+
if (lambdaEnvironment.shouldLogStructuredJson)
181+
then lambdaEnvironment.getLogger().log(message, LogLevel.INFO)
182+
else
183+
lambdaEnvironment
184+
.getLogger()
185+
.log(
186+
s"[${lambdaEnvironment.getFunctionName()}] $message",
187+
LogLevel.INFO
188+
)
189+
190+
inline def debug(
191+
message: => String
192+
)(using lambdaEnvironment: LambdaEnvironment): Unit =
193+
if (lambdaEnvironment.isDebugMode)
194+
then
195+
if (lambdaEnvironment.shouldLogStructuredJson)
196+
then lambdaEnvironment.getLogger().log(message, LogLevel.DEBUG)
197+
else
198+
lambdaEnvironment
199+
.getLogger()
200+
.log(
201+
s"${AnsiColor.BLUE}[${lambdaEnvironment
202+
.getFunctionName()}] $message${AnsiColor.RESET}",
203+
LogLevel.DEBUG
204+
)
205+
else ()
206+
207+
inline def trace(
208+
message: => String
209+
)(using lambdaEnvironment: LambdaEnvironment): Unit =
210+
if (lambdaEnvironment.isTraceMode)
211+
then
212+
if (lambdaEnvironment.shouldLogStructuredJson)
213+
then lambdaEnvironment.getLogger().log(message, LogLevel.TRACE)
214+
else
215+
lambdaEnvironment
216+
.getLogger()
217+
.log(
218+
s"${AnsiColor.BLUE}[${lambdaEnvironment
219+
.getFunctionName()}] $message${AnsiColor.RESET}",
220+
LogLevel.TRACE
221+
)
222+
else ()
223+
224+
inline def error(
225+
message: String
226+
)(using lambdaEnvironment: LambdaEnvironment): Unit =
227+
if (lambdaEnvironment.shouldLogStructuredJson)
228+
then lambdaEnvironment.getLogger().log(message, LogLevel.ERROR)
229+
else
230+
lambdaEnvironment
231+
.getLogger()
232+
.log(
233+
s"${AnsiColor.RED_B}${AnsiColor.WHITE}[${lambdaEnvironment
234+
.getFunctionName()}][ERROR] $message${AnsiColor.RESET}",
235+
LogLevel.ERROR
236+
)
237+
}
238+
}

0 commit comments

Comments
 (0)