{"id":40446711,"url":"https://github.com/serverless-guru/logger-typescript","last_synced_at":"2026-01-20T17:02:15.299Z","repository":{"id":258359137,"uuid":"864033156","full_name":"serverless-guru/logger-typescript","owner":"serverless-guru","description":"Common logger utility","archived":false,"fork":false,"pushed_at":"2025-04-10T08:35:56.000Z","size":213,"stargazers_count":1,"open_issues_count":3,"forks_count":2,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-09-27T01:55:51.883Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/serverless-guru.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-09-27T11:18:43.000Z","updated_at":"2025-04-10T08:35:36.000Z","dependencies_parsed_at":"2024-10-18T16:33:29.060Z","dependency_job_id":"14022858-e63d-4681-bf52-8b5869ed18c1","html_url":"https://github.com/serverless-guru/logger-typescript","commit_stats":null,"previous_names":["serverless-guru/logger-typescript"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/serverless-guru/logger-typescript","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serverless-guru%2Flogger-typescript","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serverless-guru%2Flogger-typescript/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serverless-guru%2Flogger-typescript/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serverless-guru%2Flogger-typescript/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/serverless-guru","download_url":"https://codeload.github.com/serverless-guru/logger-typescript/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serverless-guru%2Flogger-typescript/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28607624,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-20T16:10:39.856Z","status":"ssl_error","status_checked_at":"2026-01-20T16:10:39.493Z","response_time":117,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-20T17:02:14.034Z","updated_at":"2026-01-20T17:02:15.280Z","avatar_url":"https://github.com/serverless-guru.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Logger\n\nLogger is an opinionated logger utility for Javascript. Its aim is to simplify log analysis with [CloudWatchLogs Insight](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/AnalyzingLogData.html).\n\n## Key features\n\n- Small footprint\n- Enforces structured and consistent logs across all your Lambda functions\n- Automatically masks sensible values\n- Automatically compresses large payloads (\u003e25Kb)\n- Automatically ignores very large payloads (\u003e60Kb)\n- Supports CloudwatchLogs text and JSON format\n\n### Environment variables\n\n- `SG_LOGGER_LOG_EVENT`: Log event, _default: true_\n- `SG_LOGGER_SKIP_MASK`: Skip masking of sensible values, _default: false_\n- `SG_LOGGER_MAX_SIZE`: Skip logging payload bigger than size (in bytes), _default: 60000_\n- `SG_LOGGER_NO_SKIP`: Don't skip payloads bigger than _SG_LOGGER_MAX_SIZE_, _default: false_\n- `SG_LOGGER_COMPRESS_SIZE`: Compress (gzip) payload bigger than size (in bytes), _default: 25000_\n- `SG_LOGGER_NO_COMPRESS`: Don't compress logs bigger than `SG_LOGGER_COMPRESS_SIZE`, _default: false_\n- `SG_LOGGER_LOG_TS`: Add timestamp (in ms) to the output object (useful when not using Cloudwatch Logs), _default: false_\n- `SG_LOGGER_LOG_LEVEL`: The minimum level to log. One of `debug`, `info`, `warn` or `error`, _default: same as `AWS_LAMBDA_LOG_LEVEL` or `warn`_\n\n## Log schema\n\n```json\n{\n    \"timestamp\": 1729066777619,\n    \"service\": \"myService\",\n    \"level\": \"INFO\",\n    \"correlationId\": \"092f5cf0-d1c8-4a71-a8a0-3c86aeb1c212\",\n    \"message\": \"my message\",\n    \"context\": {\n        \"handlerNamespace\": \"multiply\",\n        \"factor\": 2\n    },\n    \"payload\": {\n        \"key1\": \"value1\",\n        \"key2\": 3,\n        \"key3\": {\n            \"key31\": \"value31\"\n        }\n    }\n}\n```\n\n### Error schema\n\n#### Without an Error object\n\n`logger.error('global error', {key1: 'value1'})`\n\n```json\n{\n    \"timestamp\": 1729066777619,\n    \"service\": \"myService\",\n    \"level\": \"ERROR\",\n    \"correlationId\": \"3bfd61c4-8934-4ae9-b646-d57144094986\",\n    \"message\": \"invalid factor\",\n    \"context\": {\n        \"handlerNamespace\": \"multiply\",\n        \"factor\": 2\n    },\n    \"payload\": {\n        \"key1\": \"value1\"\n    }\n}\n```\n\n#### With an Error object\n\n```javascript\nlogger.error(\n    \"global error\",\n    new RangeError(\"invalid factor\", {\n        cause: {\n            factor: event.factor,\n            limit: 10,\n            reason: \"too big\",\n        },\n    })\n);\n```\n\n```json\n{\n    \"timestamp\": 1729066777619,\n    \"service\": \"myService\",\n    \"level\": \"ERROR\",\n    \"correlationId\": \"3bfd61c4-8934-4ae9-b646-d57144094986\",\n    \"message\": \"global error\",\n    \"context\": {\n        \"handlerNamespace\": \"multiply\",\n        \"factor\": 2\n    },\n    \"error\": {\n        \"name\": \"RangeError\",\n        \"location\": \"/path/to/file.js:341\",\n        \"message\": \"invalid factor\",\n        \"stack\": \"RangeError: invalid factor\\n    at main (/path/to/file.js:341:15)\\n    at /path/to/file2.js:953:30\\n    at new Promise (\u003canonymous\u003e)\\n    at AwsInvokeLocal.invokeLocalNodeJs (/path/to/file3.js:906:12)\\n    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\",\n        \"cause\": {\n            \"factor\": 12,\n            \"limit\": 10,\n            \"reason\": \"too big\"\n        }\n    }\n}\n```\n\n## Installation\n\n```bash\nnpm i --save @serverless-guru/logger\n```\n\n## Usage\n\nThe `Logger` instance can be re-used across modules, allowing to keep globally defined context keys.\n\n**helpers/logger.js**\n\n```javascript\nconst { Logger } = require(\"@serverless-guru/logger\");\nconst logger = new Logger(\"myService\", \"myFirstApplication\");\nconst metricUnits = Logger.METRIC_UNITS;\nmodule.exports = { logger, metricUnits };\n```\n\n**helpers/math.js**\n\n```javascript\nconst { logger } = require(\"./logger\");\n\nexport const multiply = async (n, factor) =\u003e {\n    const sleepMs = Math.floor(Math.random() * 1000 * factor);\n\n    await delay(sleepMs);\n\n    const result = n * factor;\n\n    logger.debug(\"Multiply\", { n, duration: sleepMs, result });\n\n    return result;\n};\n\nconst delay = (ms) =\u003e {\n    return new Promise((resolve) =\u003e setTimeout(resolve, ms));\n};\n```\n\n**handlers/multiply.js**\n\n```javascript\nconst { logger, metricUnits } = require(\"../helpers/logger.js\");\nconst LOG_FORMAT = process.env.AWS_LAMBDA_LOG_FORMAT || \"Text\";\n\nconst main = async (event, context) =\u003e {\n    try {\n        logger.setCorrelationId(context.awsRequestId);\n        logger.addContextKey({\n            handlerNamespace: \"multiply\",\n            logFormat: LOG_FORMAT,\n        });\n        logger.logInputEvent({ event });\n\n        if (event.factor) {\n            logger.addContextKey({ factor: event.factor });\n            if (event.factor \u003e 10) {\n                const cause = { factor: event.factor, limit: 10, reason: \"too big\" };\n                logger.error(\"invalid factor\", cause);\n                throw new RangeError(\"invalid factor\", { cause });\n            }\n        }\n\n        const start = new Date().getTime();\n        const promises = [1, 2, 3, 4, 5].map((n) =\u003e multiply(n, event.factor || 1));\n        const result = await Promise.all(promises);\n        const end = new Date().getTime();\n\n        logger.info(\"Result\", { result }, {}, [\"factor\"]);\n\n        logger.metric(\"multiply\", {\n            name: \"Duration\",\n            unit: metricUnits.Milliseconds,\n            value: end - start,\n            dimensions: [[\"LOG_FORMAT\", LOG_FORMAT]],\n        });\n    } catch (error) {\n        logger.error(\"global error\", error);\n    } finally {\n        logger.clearLogContext();\n    }\n};\n\nmodule.exports = { main };\n```\n\n## The importance of CorrelationId\n\nWhy define a _correlationId_ when we already have a _requestId_ provided by AWS?.\n\nThe _requestId_ is unique inside a single Lambda invocation. A correlationId can be passed to other services and allows to extract logs from multiple services invoked during a specific activity.\n\nLet's consider the case of a Lambda behind an API Gateway. This function sends a message to SQS, which is then processed by another Lambda function invoking a remote API.\n\n- The Web client generates a correlationId and passes it in the payload to API Gateway (API Gateway Logs are set to log Payloads in JSON)\n- The first Lambda uses the `setCorrelationId` method to assign the `correlationId` from the payload to all log outputs\n- The `correlationId` is part of the payload sent to SQS\n- The second Lambda uses the `setCorrelationId` method to assign the `correlationId` from the SQS event to all log outputs\n- The `correlationId` is added to the invocation payload of the remote API.\n\nUsing CloudWatchLogs insight, it is now possible to query simultaneously both Lambda LogGroups, API Gateway LogGroup with a single simple query:\n\n```\nfields @timestamp, @message\n| filter correlationId=\"092f5cf0-d1c8-4a71-a8a0-3c86aeb1c212\"\n| limit 200\n```\n\nTo get the logs of all events for the specific `correlationId` across multiple services.\n\n## Class methods\n\n### Constructor\n\n```javascript\nconst logger = new Logger(serviceName, applicationName, options);\n```\n\n- **serviceName** [string, mandatory]: Added to each log output\n- **applicationName** [string, mandatory]: Defines the Namespace for metrics\n- **options** [object | null, optional]: Configuration options\n  - **correlationId** [string, optional]: A new UUIDv4 is generated when not defined. Added to each log output.\n  - **additionalSensitiveAttributes** [string[], optional]: Add new sensitive attributes to the pre-defined defaults\n  - **overrideSensitiveAttributes** [string[], optional]: Completely override the default sensitive attributes\n\nFor backward compatibility, you can also pass a string as the third parameter which will be treated as the correlationId (this usage is deprecated):\n\n```javascript\n// New style with options object\nconst logger = new Logger(\"myService\", \"myApp\", {\n    correlationId: \"custom-id\",\n    additionalSensitiveAttributes: [\"customSecret\"]\n});\n\n// Old style with string correlationId (deprecated)\nconst logger = new Logger(\"myService\", \"myApp\", \"custom-id\");\n```\n\n### setCorrelationId\n\nSet a correlationId used across all log statements. Useful when the _correlationId_ is received as payload to the Lambda function.\n\n```javascript\nlogger.setCorrelationId(correlationId);\n```\n\n- **correlationId** [string, mandatory]\n\n### getCorrelationId\n\nRetrieves the current _correlationId_. Useful when the correlationId needs to be passed to API calls or other service integrations.\n\n```javascript\nconst correlationId = logger.getCorrelationId();\n```\n\n- **correlationId** [string, mandatory]\n\n### logInputEvent\n\nLogs the object passed as argument when the environment variable _LOG_EVENT_ is set to _\"true\"_. Generally used to conditionally log the incoming event, but it can be used for any other payload too.\n\nThe _message_ key will always be `Input Event`.\n\n```javascript\nlogger.logInputEvent(payload);\n```\n\n- **payload** [object]\n\n#### Example\n\nTo conditionally log the incoming event, the Lambda context and the environment variables:\n\n```javascript\nlogger.logInputEvent({ event, context, env: process.env });\n```\n\n### addContextKey\n\nAdd keys to the context object. Keys added to the context are available in all log outputs under the top level `context` key.\nuseful to automatically add values to all future logs.\n\n```javascript\nlogger.addContextKey(contextObject);\n```\n\n- **contextObject**: [object]\n\n### clearLogContext\n\nClears the all context keys. This needs to be invoked at the end of each Lambda invocation to avoid re-using context keys across subsequent invocation.\n\n```javascript\nlogger.clearLogContext();\n```\n\n### log\n\nPrints a log message.\n\n```javascript\nlogger.log(level, message, payload, context, sensitiveAttributes);\n```\n\n- **level** [string, mandatory]: one of `info`, `debug`, `warn`, `error`\n- **message** [string, mandatory]: Assigned to the output `message`. It is good practice to keep it concise and describe the activity. Re-use the same message across multiple logs, identify the individual activities using context or payload values.\n- **payload** [string, object]: The payload to log\n- **context** [object]: Keys to add to the context of this log output\n- **sensitiveAttributes** [array of string]: Additional attributes to mask in this log output\n\n#### Shorthand\n\n- logger.info(message, payload, context, sensitiveAttributes)\n- logger.debug(message, payload, context, sensitiveAttributes)\n- logger.warn(message, payload, context, sensitiveAttributes)\n- logger.error(message, payload, context, sensitiveAttributes)\n\n#### Default masked attributes\n\nAny key, be it in the `payload` or the `context`, having one of this values will be masked in the output:\n\n- password\n- userid\n- token\n- secret\n- key\n- x-api-key\n- bearer\n- authorization\n\nMasking can be disabled, by setting the environment variable `LOG_MASK=\"false\"`.\n\n### Max log level\n\nWhen `SG_LOGGER_LOG_LEVEL` is set, only log levels equal or greater than the specified value will be logged. Log levels are in the following order of importance.\n\n`debug` \u003c `info` \u003c `warn` \u003c `error`\n\n**Note**: When use with AWS Lambda, and when `AWS_LAMBDA_LOG_LEVEL` is set to a stricter level than `SG_LOGGER_LOG_LEVEL`, ALC will drop logs emitted by the logger that don't match `AWS_LAMBDA_LOG_LEVEL`.\n\n### metric\n\nThis generates a log output in [EMF](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format_Specification.html) format, creating a metric in [Cloudwatch Metrics](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/working_with_metrics.html).\nThe metrics will be available under the namespace defined by `this.applicationName`.\n\n```javascript\nlogger.metric(activity: string, meta: MetricMeta)\n```\n\n- **activity** [string, mandatory]: The default dimension\n- **meta** [object]\n    - **name** [string]: The name of the metric\n    - **value** [number]: The value of the metric\n    - **unit** [string]: The unit of the metric (see Logger.METRIC_UNITS)\n    - **dimensions** [Array of String pairs]: Additional dimensions for the metric. `[[name1, value1], [name2, value2]]`\n\n**Note**: To be able to use EMF, your log group needs to be sert to _standard_ (default) and not _infrequent access_.\n\n## CloudWatchLogs logFormat\n\nLambda allows to use CloudWatchLogs Structured format (recommended), which not only stores the logs in JSON, but also allows to set the log level directly on the log group.\n\n### Configure with [Serverless Framework](https://www.serverless.com)\n\n#### Serverless v3\n\nServerless V3 doesn't allow to set the format directly from the function. You need to configure it via Cloudformation resources by extending the definition of the function generated by the framework.\n\nThe logical key for a function in Cloudformation is the logical key of the function with the suffix `LambaFunction`.\n\n```yaml\nservice: myService\nprovider:\n    name: aws\n    runtime: nodejs20.x\n    architecture: \"arm64\"\nfunctions:\n    Multiply:\n        handler: src/handlers/multiply.handler\n        name: multiply\n        environment:\n            LOG_EVENT: \"true\"\nresources:\n    resources:\n        MultiplyLambdaFunction:\n            Type: AWS::Lambda::Function\n            Properties:\n                LoggingConfig:\n                    LogFormat: JSON\n                    ApplicationLogLevel: WARN\n                    SystemLogLevel: INFO\n```\n\n#### Serverless v4\n\nWith Serverless v4, the logFormat can be directly defined in the framework definition, either globally under `provider` or per function.\n\n```yaml\nservice: myService\nprovider:\n    name: aws\n    runtime: nodejs20.x\n    architecture: \"arm64\"\n    logs:\n        lambda:\n            logFormat: JSON\n            applicationLogLevel: WARN\n            systemLogLevel: INFO\nfunctions:\n    Multiply:\n        handler: src/handlers/multiply.handler\n        name: multiply\n        environment:\n            LOG_EVENT: \"true\"\n        logs:\n            logFormat: JSON\n            applicationLogLevel: WARN\n            systemLogLevel: INFO\n```\n\n### Sensitive Attributes\n\nThe logger automatically masks sensitive values in your logs. By default, it masks common sensitive fields like passwords, tokens, and API keys. You can customize this behavior in two ways:\n\n1. **Add to Defaults**: Use `additionalSensitiveAttributes` to add new fields to the default list:\n```javascript\nconst logger = new Logger(\"myService\", \"myApp\", {\n    additionalSensitiveAttributes: [\"customSecret\", \"apiToken\"]\n});\n```\n\n2. **Override Defaults**: Use `overrideSensitiveAttributes` to completely replace the default list:\n```javascript\nconst logger = new Logger(\"myService\", \"myApp\", {\n    overrideSensitiveAttributes: [\"onlyThese\", \"willBeMasked\"]\n});\n```\n\nYou can also specify sensitive attributes per log call:\n```javascript\n// Add to defaults\nlogger.info(\"message\", payload, context, [\"customSecret\"]);\n\n// Or use the object syntax\nlogger.info(\"message\", payload, context, {\n    additionalSensitiveAttributes: [\"customSecret\"]\n});\n\n// Or override completely\nlogger.info(\"message\", payload, context, {\n    overrideSensitiveAttributes: [\"onlyThese\", \"willBeMasked\"]\n});\n```\n\nThe default sensitive attributes are:\n- password\n- userid\n- token\n- secret\n- key\n- x-api-key\n- bearer\n- authorization\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fserverless-guru%2Flogger-typescript","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fserverless-guru%2Flogger-typescript","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fserverless-guru%2Flogger-typescript/lists"}