@dash0/serverless-dash0 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # serverless-dash0
2
+
3
+ Serverless Framework plugin that automatically adds the [Dash0](https://www.dash0.com) Lambda extension layer and environment variables to your functions.
4
+
5
+ Compatible with Serverless Framework v3 and v4.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install --save-dev serverless-dash0
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ Add the plugin to your `serverless.yml` and configure the `custom.dash0` section:
16
+
17
+ ```yaml
18
+ plugins:
19
+ - serverless-dash0
20
+
21
+ custom:
22
+ dash0:
23
+ endpoint: https://ingress.eu-west-1.aws.dash0.com:4318
24
+ token: ${env:DASH0_TOKEN}
25
+ layerVersion: latest
26
+
27
+ functions:
28
+ myFunction:
29
+ handler: handler.hello
30
+ runtime: nodejs20.x
31
+ dash0-traced: true
32
+
33
+ untracedFunction:
34
+ handler: handler.other
35
+ runtime: nodejs20.x
36
+ ```
37
+
38
+ Only functions with `dash0-traced: true` will have the Dash0 layer and environment variables added. All other functions are left untouched.
39
+
40
+ ### Supported runtimes
41
+
42
+ | Runtime | Layer |
43
+ | --------------- | ------------------------ |
44
+ | `nodejs*` | `dash0-extension-node` |
45
+ | `python*` | `dash0-extension-python` |
46
+ | `java*` | `dash0-extension-java` |
47
+
48
+ Container image functions (`image` instead of `handler`) are not supported. Use the [Dash0 Docker images](https://github.com/dash0hq/dash0-lambda-extension#docker) instead.
49
+
50
+ ## Configuration reference
51
+
52
+ All options are set under `custom.dash0` in your `serverless.yml`.
53
+
54
+ ### Required
55
+
56
+ | Option | Type | Description |
57
+ | --- | --- | --- |
58
+ | `endpoint` | `string` | Dash0 OTLP ingestion endpoint. |
59
+ | `layerVersion` | `number` or `"latest"` | Layer version to use. A specific version number, or `"latest"` to use the version bundled with the plugin. |
60
+ | `token` | `string` | Dash0 API token. Required unless `tokenSecretArn` is provided. |
61
+ | `tokenSecretArn` | `string` | ARN of an AWS Secrets Manager secret containing the Dash0 API token. Required unless `token` is provided. If both are set, both environment variables will be added. |
62
+
63
+ ### Optional
64
+
65
+ | Option | Type | Default | Env var set | Description |
66
+ | --- | --- | --- | --- | --- |
67
+ | `tokenSecretKey` | `string` | - | `DASH0_TOKEN_SECRET_KEY` | JSON key to extract the token from the secret (when the secret value is a JSON object). |
68
+ | `dataset` | `string` | - | `DASH0_DATASET` | Route telemetry to a specific Dash0 dataset. |
69
+ | `extensionLogLevel` | `string` | `warn` | `DASH0_EXTENSION_LOG_LEVEL` | Log level for the extension. One of: `trace`, `debug`, `info`, `warn`, `error`. |
70
+ | `disableAutoInstrumentation` | `boolean` | `false` | `DASH0_DISABLE_AUTO_INSTRUMENTATION` | Disable auto-instrumentation. The extension will still create synthetic spans. |
71
+ | `sendOnInvocationEnd` | `boolean` | `true` | `DASH0_SEND_ON_INVOCATION_END` | Send telemetry at the end of the current invocation instead of the beginning of the next. |
72
+ | `disableTelemetryLogCollection` | `boolean` | `false` | `DASH0_DISABLE_TELEMETRY_LOG_COLLECTION` | Disable Lambda Telemetry API log collection. |
73
+ | `createPayloadLogRecords` | `boolean` | `true` | `DASH0_CREATE_PAYLOAD_LOG_RECORDS` | Create log records for request/response payloads. |
74
+ | `requestTimeout` | `number` | `2000` | `DASH0_REQUEST_TIMEOUT` | HTTP request timeout in milliseconds. |
75
+ | `xrayTracesEnabled` | `boolean` | `false` | `DASH0_XRAY_TRACES_ENABLED` | Preserve X-Ray trace context. |
76
+ | `maskRules` | `string` | - | `DASH0_MASK_RULES` | JSON array of regex patterns for masking sensitive data. |
77
+ | `maskEnvVars` | `string` | - | `DASH0_MASK_ENV_VARS` | JSON array of regex patterns for masking environment variable values. |
78
+
79
+ ### Behavior
80
+
81
+ - The plugin runs at the `before:package:initialize` lifecycle hook.
82
+ - Existing function layers are preserved; the Dash0 layer is appended.
83
+ - Existing function environment variables are never overwritten. If you set `DASH0_EXTENSION_LOG_LEVEL` on a specific function, the plugin will not replace it with the global value.
84
+ - Functions with unsupported runtimes or container images are skipped with a warning.
@@ -0,0 +1,20 @@
1
+ export interface Dash0Config {
2
+ endpoint: string;
3
+ layerVersion: number | 'latest';
4
+ layerAccountId?: string;
5
+ layerNamePrefix?: string;
6
+ token?: string;
7
+ tokenSecretArn?: string;
8
+ tokenSecretKey?: string;
9
+ dataset?: string;
10
+ extensionLogLevel?: string;
11
+ disableAutoInstrumentation?: boolean | string;
12
+ sendOnInvocationEnd?: boolean | string;
13
+ disableTelemetryLogCollection?: boolean | string;
14
+ createPayloadLogRecords?: boolean | string;
15
+ requestTimeout?: number;
16
+ xrayTracesEnabled?: boolean | string;
17
+ maskRules?: string;
18
+ maskEnvVars?: string;
19
+ }
20
+ export declare function validateConfig(config: unknown): Dash0Config;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateConfig = validateConfig;
4
+ const VALID_LOG_LEVELS = ['trace', 'debug', 'info', 'warn', 'error'];
5
+ function validateConfig(config) {
6
+ if (!config || typeof config !== 'object') {
7
+ throw new Error('[serverless-dash0] Missing "custom.dash0" configuration block');
8
+ }
9
+ const c = config;
10
+ if (!c.endpoint || typeof c.endpoint !== 'string') {
11
+ throw new Error('[serverless-dash0] "custom.dash0.endpoint" is required and must be a string');
12
+ }
13
+ if (c.layerVersion === undefined || c.layerVersion === null) {
14
+ throw new Error('[serverless-dash0] "custom.dash0.layerVersion" is required (positive integer or "latest")');
15
+ }
16
+ if (c.layerVersion !== 'latest') {
17
+ const parsed = Number(c.layerVersion);
18
+ if (!Number.isInteger(parsed) || parsed < 1) {
19
+ throw new Error('[serverless-dash0] "custom.dash0.layerVersion" must be a positive integer or "latest"');
20
+ }
21
+ c.layerVersion = parsed;
22
+ }
23
+ if (c.layerAccountId !== undefined) {
24
+ c.layerAccountId = String(c.layerAccountId);
25
+ }
26
+ if (!c.token && !c.tokenSecretArn) {
27
+ throw new Error('[serverless-dash0] Either "custom.dash0.token" or "custom.dash0.tokenSecretArn" must be provided');
28
+ }
29
+ if (c.extensionLogLevel !== undefined) {
30
+ if (!VALID_LOG_LEVELS.includes(c.extensionLogLevel)) {
31
+ throw new Error(`[serverless-dash0] "custom.dash0.extensionLogLevel" must be one of: ${VALID_LOG_LEVELS.join(', ')}`);
32
+ }
33
+ }
34
+ return c;
35
+ }
@@ -0,0 +1,2 @@
1
+ import { Dash0Config } from './config-validator';
2
+ export declare function buildEnvironment(config: Dash0Config): Record<string, string>;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildEnvironment = buildEnvironment;
4
+ const OPTIONAL_MAPPINGS = {
5
+ dataset: 'DASH0_DATASET',
6
+ extensionLogLevel: 'DASH0_EXTENSION_LOG_LEVEL',
7
+ disableAutoInstrumentation: 'DASH0_DISABLE_AUTO_INSTRUMENTATION',
8
+ sendOnInvocationEnd: 'DASH0_SEND_ON_INVOCATION_END',
9
+ disableTelemetryLogCollection: 'DASH0_DISABLE_TELEMETRY_LOG_COLLECTION',
10
+ createPayloadLogRecords: 'DASH0_CREATE_PAYLOAD_LOG_RECORDS',
11
+ requestTimeout: 'DASH0_REQUEST_TIMEOUT',
12
+ xrayTracesEnabled: 'DASH0_XRAY_TRACES_ENABLED',
13
+ maskRules: 'DASH0_MASK_RULES',
14
+ maskEnvVars: 'DASH0_MASK_ENV_VARS',
15
+ };
16
+ function buildEnvironment(config) {
17
+ const env = {};
18
+ env.AWS_LAMBDA_EXEC_WRAPPER = '/opt/wrapper';
19
+ env.DASH0_ENDPOINT = config.endpoint;
20
+ if (config.token) {
21
+ env.DASH0_TOKEN = config.token;
22
+ }
23
+ if (config.tokenSecretArn) {
24
+ env.DASH0_TOKEN_SECRET_ARN = config.tokenSecretArn;
25
+ }
26
+ if (config.tokenSecretKey) {
27
+ env.DASH0_TOKEN_SECRET_KEY = config.tokenSecretKey;
28
+ }
29
+ for (const [configKey, envVar] of Object.entries(OPTIONAL_MAPPINGS)) {
30
+ const value = config[configKey];
31
+ if (value !== undefined && value !== null) {
32
+ env[envVar] = String(value);
33
+ }
34
+ }
35
+ return env;
36
+ }
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const config_validator_1 = require("./config-validator");
37
+ const runtime_mapping_1 = require("./runtime-mapping");
38
+ const layer_arn_builder_1 = require("./layer-arn-builder");
39
+ const env_injector_1 = require("./env-injector");
40
+ const bundledVersions = __importStar(require("./versions.json"));
41
+ class ServerlessDash0Plugin {
42
+ constructor(serverless, options, utils) {
43
+ this.serverless = serverless;
44
+ this.options = options;
45
+ this.log = utils?.log
46
+ ? (msg) => utils.log.notice(msg)
47
+ : (msg) => serverless.cli.log(msg);
48
+ this.warn = utils?.log
49
+ ? (msg) => utils.log.warning(msg)
50
+ : (msg) => serverless.cli.log(`WARNING: ${msg}`);
51
+ this.hooks = {
52
+ 'before:package:initialize': () => this.addDash0Tracing(),
53
+ };
54
+ }
55
+ getBundledVersion(layerName) {
56
+ const version = bundledVersions[layerName];
57
+ if (!version) {
58
+ throw new Error(`[serverless-dash0] No bundled version found for layer "${layerName}". Please specify an explicit layerVersion.`);
59
+ }
60
+ return version;
61
+ }
62
+ addDash0Tracing() {
63
+ const dash0Config = (0, config_validator_1.validateConfig)(this.serverless.service.custom?.dash0);
64
+ const region = this.options.region ||
65
+ this.serverless.service.provider.region ||
66
+ process.env.AWS_REGION ||
67
+ process.env.AWS_DEFAULT_REGION ||
68
+ 'us-east-1';
69
+ const dash0Env = (0, env_injector_1.buildEnvironment)(dash0Config);
70
+ const functions = this.serverless.service.functions;
71
+ const useLatest = dash0Config.layerVersion === 'latest';
72
+ for (const [funcName, funcDef] of Object.entries(functions)) {
73
+ const traced = funcDef['dash0-traced'];
74
+ if (traced !== true && traced !== 'true') {
75
+ continue;
76
+ }
77
+ if (funcDef.image) {
78
+ this.warn(`[serverless-dash0] Skipping function "${funcName}": container image functions cannot use Lambda layers. Use the Dash0 Docker image instead.`);
79
+ continue;
80
+ }
81
+ const runtime = funcDef.runtime || this.serverless.service.provider.runtime;
82
+ const layerName = (0, runtime_mapping_1.resolveLayerName)(runtime);
83
+ if (!layerName) {
84
+ this.warn(`[serverless-dash0] Skipping function "${funcName}": unsupported runtime "${runtime ?? 'undefined'}". Supported: nodejs*, python*, java*.`);
85
+ continue;
86
+ }
87
+ const layerVersion = useLatest
88
+ ? this.getBundledVersion(layerName)
89
+ : dash0Config.layerVersion;
90
+ const layerArn = (0, layer_arn_builder_1.buildLayerArn)(region, layerName, layerVersion, dash0Config.layerAccountId, dash0Config.layerNamePrefix);
91
+ // Append layer (no duplicates)
92
+ funcDef.layers = funcDef.layers || [];
93
+ if (!funcDef.layers.includes(layerArn)) {
94
+ funcDef.layers.push(layerArn);
95
+ }
96
+ // Merge env vars (never overwrite existing)
97
+ funcDef.environment = funcDef.environment || {};
98
+ for (const [key, value] of Object.entries(dash0Env)) {
99
+ if (funcDef.environment[key] === undefined) {
100
+ funcDef.environment[key] = value;
101
+ }
102
+ }
103
+ this.log(`[serverless-dash0] Configured tracing for function "${funcName}" (${runtime}, layer v${layerVersion})`);
104
+ }
105
+ }
106
+ }
107
+ module.exports = ServerlessDash0Plugin;
@@ -0,0 +1,2 @@
1
+ export declare const DEFAULT_LAYER_ACCOUNT_ID = "115813213817";
2
+ export declare function buildLayerArn(region: string, layerName: string, layerVersion: number, accountId?: string, layerNamePrefix?: string): string;
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_LAYER_ACCOUNT_ID = void 0;
4
+ exports.buildLayerArn = buildLayerArn;
5
+ exports.DEFAULT_LAYER_ACCOUNT_ID = '115813213817';
6
+ function buildLayerArn(region, layerName, layerVersion, accountId, layerNamePrefix) {
7
+ const account = accountId || exports.DEFAULT_LAYER_ACCOUNT_ID;
8
+ const prefixedName = layerNamePrefix ? `${layerNamePrefix}${layerName}` : layerName;
9
+ return `arn:aws:lambda:${region}:${account}:layer:${prefixedName}:${layerVersion}`;
10
+ }
@@ -0,0 +1 @@
1
+ export declare function resolveLayerName(runtime: string | undefined): string | null;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveLayerName = resolveLayerName;
4
+ const RUNTIME_MAP = {
5
+ node: 'dash0-extension-node',
6
+ python: 'dash0-extension-python',
7
+ java: 'dash0-extension-java',
8
+ };
9
+ function resolveLayerName(runtime) {
10
+ if (!runtime || typeof runtime !== 'string') {
11
+ return null;
12
+ }
13
+ const normalized = runtime.toLowerCase();
14
+ for (const [prefix, layerName] of Object.entries(RUNTIME_MAP)) {
15
+ if (normalized.startsWith(prefix)) {
16
+ return layerName;
17
+ }
18
+ }
19
+ return null;
20
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "dash0-extension-node": 5,
3
+ "dash0-extension-python": 5,
4
+ "dash0-extension-java": 5
5
+ }
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@dash0/serverless-dash0",
3
+ "version": "1.0.0",
4
+ "description": "Serverless Framework plugin to automatically add Dash0 Lambda tracing layers and configuration",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "test": "jest",
10
+ "prepublishOnly": "npm run build"
11
+ },
12
+ "keywords": [
13
+ "serverless",
14
+ "serverless-plugin",
15
+ "dash0",
16
+ "lambda",
17
+ "tracing",
18
+ "opentelemetry",
19
+ "observability"
20
+ ],
21
+ "author": "Dash0",
22
+ "license": "Apache-2.0",
23
+ "devDependencies": {
24
+ "@types/jest": "^29.5.14",
25
+ "jest": "^29.7.0",
26
+ "ts-jest": "^29.3.2",
27
+ "typescript": "^5.8.3"
28
+ },
29
+ "peerDependencies": {
30
+ "serverless": ">=3.0.0"
31
+ },
32
+ "peerDependenciesMeta": {
33
+ "serverless": {
34
+ "optional": true
35
+ }
36
+ },
37
+ "engines": {
38
+ "node": ">=16.0.0"
39
+ },
40
+ "files": ["dist/", "README.md"]
41
+ }