|
| 1 | +# JSON Logging Example |
| 2 | + |
| 3 | +This example demonstrates how to use structured JSON logging with AWS Lambda functions written in Swift. When configured with JSON log format, your logs are automatically structured as JSON objects, making them easier to search, filter, and analyze in CloudWatch Logs. |
| 4 | + |
| 5 | +## Features |
| 6 | + |
| 7 | +- Structured JSON log output |
| 8 | +- Automatic inclusion of request ID and trace ID |
| 9 | +- Support for all log levels (TRACE, DEBUG, INFO, WARN, ERROR, FATAL) |
| 10 | +- Custom metadata in logs |
| 11 | +- Compatible with CloudWatch Logs Insights queries |
| 12 | + |
| 13 | +## Code |
| 14 | + |
| 15 | +The Lambda function demonstrates various logging levels and metadata usage. When `AWS_LAMBDA_LOG_FORMAT` is set to `JSON`, all logs are automatically formatted as JSON objects with the following structure: |
| 16 | + |
| 17 | +```json |
| 18 | +{ |
| 19 | + "timestamp": "2024-10-27T19:17:45.586Z", |
| 20 | + "level": "INFO", |
| 21 | + "message": "Processing request for Alice", |
| 22 | + "requestId": "79b4f56e-95b1-4643-9700-2807f4e68189", |
| 23 | + "traceId": "Root=1-67890abc-def12345678901234567890a" |
| 24 | +} |
| 25 | +``` |
| 26 | + |
| 27 | +## Configuration |
| 28 | + |
| 29 | +### Environment Variables |
| 30 | + |
| 31 | +- `AWS_LAMBDA_LOG_FORMAT`: Set to `JSON` for structured logging (default: `Text`) |
| 32 | +- `AWS_LAMBDA_LOG_LEVEL`: Control which logs are sent to CloudWatch |
| 33 | + - Valid values: `TRACE`, `DEBUG`, `INFO`, `WARN`, `ERROR`, `FATAL` |
| 34 | + - Default: `INFO` when JSON format is enabled |
| 35 | + |
| 36 | +### SAM Template Configuration |
| 37 | + |
| 38 | +Add the `LoggingConfig` property to your Lambda function: |
| 39 | + |
| 40 | +```yaml |
| 41 | +Resources: |
| 42 | + JSONLoggingFunction: |
| 43 | + Type: AWS::Serverless::Function |
| 44 | + Properties: |
| 45 | + CodeUri: .build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/JSONLogging/JSONLogging.zip |
| 46 | + Handler: swift.bootstrap |
| 47 | + Runtime: provided.al2023 |
| 48 | + Architectures: |
| 49 | + - arm64 |
| 50 | + LoggingConfig: |
| 51 | + LogFormat: JSON |
| 52 | + ApplicationLogLevel: INFO # TRACE | DEBUG | INFO | WARN | ERROR | FATAL |
| 53 | + SystemLogLevel: INFO # DEBUG | INFO | WARN |
| 54 | +``` |
| 55 | +
|
| 56 | +## Test Locally |
| 57 | +
|
| 58 | +Start the local server with TEXT logging: |
| 59 | +
|
| 60 | +```bash |
| 61 | +swift run |
| 62 | +``` |
| 63 | + |
| 64 | +Send test requests: |
| 65 | + |
| 66 | +```bash |
| 67 | +# Basic request |
| 68 | +curl -d '{"name":"Alice"}' http://127.0.0.1:7000/invoke |
| 69 | + |
| 70 | +# Request with custom level |
| 71 | +curl -d '{"name":"Bob","level":"debug"}' http://127.0.0.1:7000/invoke |
| 72 | + |
| 73 | +# Trigger error logging |
| 74 | +curl -d '{"name":"error"}' http://127.0.0.1:7000/invoke |
| 75 | +``` |
| 76 | + |
| 77 | +To test with JSON logging locally, set the environment variable: |
| 78 | + |
| 79 | +```bash |
| 80 | +AWS_LAMBDA_LOG_FORMAT=JSON swift run |
| 81 | +``` |
| 82 | + |
| 83 | +## Build & Package |
| 84 | + |
| 85 | +```bash |
| 86 | +swift build |
| 87 | +LAMBDA_USE_LOCAL_DEPS=../.. swift package archive --allow-network-connections docker |
| 88 | +``` |
| 89 | + |
| 90 | +The deployment package will be at: |
| 91 | +`.build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/JSONLogging/JSONLogging.zip` |
| 92 | + |
| 93 | +## Deploy with SAM |
| 94 | + |
| 95 | +Create a `template.yaml` file: |
| 96 | + |
| 97 | +```yaml |
| 98 | +AWSTemplateFormatVersion: '2010-09-09' |
| 99 | +Transform: AWS::Serverless-2016-10-31 |
| 100 | +Description: JSON Logging Example |
| 101 | + |
| 102 | +Resources: |
| 103 | + JSONLoggingFunction: |
| 104 | + Type: AWS::Serverless::Function |
| 105 | + Properties: |
| 106 | + CodeUri: .build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/JSONLogging/JSONLogging.zip |
| 107 | + Timeout: 60 |
| 108 | + Handler: swift.bootstrap |
| 109 | + Runtime: provided.al2023 |
| 110 | + Architectures: |
| 111 | + - arm64 |
| 112 | + LoggingConfig: |
| 113 | + LogFormat: JSON |
| 114 | + ApplicationLogLevel: DEBUG |
| 115 | + SystemLogLevel: INFO |
| 116 | + |
| 117 | +Outputs: |
| 118 | + FunctionName: |
| 119 | + Description: Lambda Function Name |
| 120 | + Value: !Ref JSONLoggingFunction |
| 121 | +``` |
| 122 | +
|
| 123 | +Deploy: |
| 124 | +
|
| 125 | +```bash |
| 126 | +sam deploy --guided |
| 127 | +``` |
| 128 | + |
| 129 | +## Deploy with AWS CLI |
| 130 | + |
| 131 | +As an alternative to SAM, you can use the AWS CLI: |
| 132 | + |
| 133 | +```bash |
| 134 | +ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text) |
| 135 | +aws lambda create-function \ |
| 136 | + --function-name JSONLoggingExample \ |
| 137 | + --zip-file fileb://.build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/JSONLogging/JSONLogging.zip \ |
| 138 | + --runtime provided.al2023 \ |
| 139 | + --handler swift.bootstrap \ |
| 140 | + --architectures arm64 \ |
| 141 | + --role arn:aws:iam::${ACCOUNT_ID}:role/lambda_basic_execution \ |
| 142 | + --logging-config LogFormat=JSON,ApplicationLogLevel=DEBUG,SystemLogLevel=INFO |
| 143 | +``` |
| 144 | + |
| 145 | +## Invoke |
| 146 | + |
| 147 | +```bash |
| 148 | +aws lambda invoke \ |
| 149 | + --function-name JSONLoggingExample \ |
| 150 | + --cli-binary-format raw-in-base64-out \ |
| 151 | + --payload '{"name":"Alice","level":"debug"}' \ |
| 152 | + response.json && cat response.json && rm response.json |
| 153 | +``` |
| 154 | + |
| 155 | +## Query Logs with CloudWatch Logs Insights |
| 156 | + |
| 157 | +With JSON formatted logs, you can use powerful queries in [CloudWatch Logs Insights](https://console.aws.amazon.com/cloudwatch/home#logsV2:logs-insights). |
| 158 | + |
| 159 | +### Using the AWS Console |
| 160 | + |
| 161 | +1. Open the [CloudWatch Logs Insights console](https://console.aws.amazon.com/cloudwatch/home#logsV2:logs-insights) |
| 162 | +2. In the "Select log group(s)" dropdown, choose the log group for your Lambda function (typically `/aws/lambda/JSONLoggingExample`) |
| 163 | +3. Type or paste one of the queries below into the query editor |
| 164 | +4. Adjust the time range in the top-right corner to cover the period you're interested in |
| 165 | +5. Click "Run query" |
| 166 | + |
| 167 | +``` |
| 168 | +# Find all ERROR level logs |
| 169 | +fields @timestamp, level, message, requestId |
| 170 | +| filter level = "ERROR" |
| 171 | +| sort @timestamp desc |
| 172 | +
|
| 173 | +# Find logs for a specific request |
| 174 | +fields @timestamp, level, message |
| 175 | +| filter requestId = "79b4f56e-95b1-4643-9700-2807f4e68189" |
| 176 | +| sort @timestamp asc |
| 177 | +
|
| 178 | +# Count logs by level |
| 179 | +stats count() by level |
| 180 | +
|
| 181 | +# Find logs with specific metadata |
| 182 | +fields @timestamp, message, metadata.errorType |
| 183 | +| filter metadata.errorType = "SimulatedError" |
| 184 | +``` |
| 185 | + |
| 186 | +### Using the AWS CLI |
| 187 | + |
| 188 | +You can also run Logs Insights queries from the command line. Each query is a two-step process: start the query, then fetch the results. |
| 189 | + |
| 190 | +```bash |
| 191 | +# 1. Start a query (adjust --start-time and --end-time as needed) |
| 192 | +QUERY_ID=$(aws logs start-query \ |
| 193 | + --log-group-name '/aws/lambda/JSONLoggingExample' \ |
| 194 | + --start-time $(date -v-1H +%s) \ |
| 195 | + --end-time $(date +%s) \ |
| 196 | + --query-string 'fields @timestamp, level, message | filter level = "ERROR" | sort @timestamp desc' \ |
| 197 | + --query 'queryId' --output text) |
| 198 | + |
| 199 | +# 2. Wait a moment for the query to complete, then get the results |
| 200 | +sleep 2 |
| 201 | +aws logs get-query-results --query-id "$QUERY_ID" |
| 202 | +``` |
| 203 | + |
| 204 | +A few more examples: |
| 205 | + |
| 206 | +```bash |
| 207 | +# Count logs by level over the last 24 hours |
| 208 | +QUERY_ID=$(aws logs start-query \ |
| 209 | + --log-group-name '/aws/lambda/JSONLoggingExample' \ |
| 210 | + --start-time $(date -v-24H +%s) \ |
| 211 | + --end-time $(date +%s) \ |
| 212 | + --query-string 'stats count() by level' \ |
| 213 | + --query 'queryId' --output text) |
| 214 | +sleep 2 |
| 215 | +aws logs get-query-results --query-id "$QUERY_ID" |
| 216 | + |
| 217 | +# Find logs with a specific error type in the last hour |
| 218 | +QUERY_ID=$(aws logs start-query \ |
| 219 | + --log-group-name '/aws/lambda/JSONLoggingExample' \ |
| 220 | + --start-time $(date -v-1H +%s) \ |
| 221 | + --end-time $(date +%s) \ |
| 222 | + --query-string 'fields @timestamp, message, metadata.errorType | filter metadata.errorType = "SimulatedError"' \ |
| 223 | + --query 'queryId' --output text) |
| 224 | +sleep 2 |
| 225 | +aws logs get-query-results --query-id "$QUERY_ID" |
| 226 | +``` |
| 227 | + |
| 228 | +> **Note**: On Linux, replace `date -v-1H +%s` with `date -d '1 hour ago' +%s` (and similarly for other time offsets). |
| 229 | +
|
| 230 | +## Log Levels |
| 231 | + |
| 232 | +The runtime maps Swift's `Logger.Level` to AWS Lambda log levels: |
| 233 | + |
| 234 | +| Swift Logger.Level | JSON Output | Description | |
| 235 | +|-------------------|-------------|-------------| |
| 236 | +| `.trace` | `TRACE` | Most detailed | |
| 237 | +| `.debug` | `DEBUG` | Debug information | |
| 238 | +| `.info` | `INFO` | Informational | |
| 239 | +| `.notice` | `INFO` | Notable events | |
| 240 | +| `.warning` | `WARN` | Warning conditions | |
| 241 | +| `.error` | `ERROR` | Error conditions | |
| 242 | +| `.critical` | `FATAL` | Critical failures | |
| 243 | + |
| 244 | +## Benefits of JSON Logging |
| 245 | + |
| 246 | +1. **Structured Data**: Logs are key-value pairs, not plain text |
| 247 | +2. **Easy Filtering**: Query specific fields in CloudWatch Logs Insights |
| 248 | +3. **Automatic Context**: Request ID and trace ID included automatically |
| 249 | +4. **Metadata Support**: Add custom fields to logs |
| 250 | +5. **No Double Encoding**: Already-JSON logs aren't double-encoded |
| 251 | +6. **Better Analysis**: Automated log analysis and alerting |
| 252 | + |
| 253 | +## Clean Up |
| 254 | + |
| 255 | +```bash |
| 256 | +# SAM deployment |
| 257 | +sam delete |
| 258 | + |
| 259 | +# AWS CLI deployment |
| 260 | +aws lambda delete-function --function-name JSONLoggingExample |
| 261 | +``` |
| 262 | + |
| 263 | +## ⚠️ Important Notes |
| 264 | + |
| 265 | +- JSON logging adds metadata, which increases log size |
| 266 | +- Default log level is `INFO` when JSON format is enabled |
| 267 | +- For Python functions, the default changes from `WARN` to `INFO` with JSON format |
| 268 | +- Logs are only formatted as JSON in the Lambda environment, not in local testing (unless you set `AWS_LAMBDA_LOG_FORMAT=JSON`) |
0 commit comments