├── .eslintrc.yml ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .nvmrc ├── LICENSE.md ├── README.md ├── index.d.ts ├── index.js ├── index.js.map ├── jest.config.ts ├── package-lock.json ├── package.json ├── src └── index.ts └── tsconfig.json /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | root: true 2 | parser: "@typescript-eslint/parser" 3 | plugins: 4 | - "@typescript-eslint" 5 | - prettier 6 | extends: 7 | - eslint:recommended 8 | - plugin:@typescript-eslint/eslint-recommended 9 | - plugin:@typescript-eslint/recommended 10 | - prettier 11 | rules: 12 | no-console: 1 13 | prettier/prettier: 2 14 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: ["main"] 5 | tags: ["*"] 6 | pull_request: 7 | branches: ["main"] 8 | 9 | jobs: 10 | test: 11 | name: Test 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | node-version: [18.x] 16 | steps: 17 | - uses: actions/checkout@v4 18 | - name: Use Node.js ${{ matrix.node-version }} 19 | uses: actions/setup-node@v4 20 | with: 21 | node-version: ${{ matrix.node-version }} 22 | - run: npm ci 23 | - run: npm run build 24 | - run: npm run lint 25 | - run: npm test 26 | 27 | publish: 28 | name: Publish 29 | runs-on: ubuntu-latest 30 | if: startsWith(github.ref, 'refs/tags/') 31 | needs: 32 | - test 33 | steps: 34 | - uses: actions/checkout@v4 35 | - uses: actions/setup-node@v4 36 | with: 37 | registry-url: https://registry.npmjs.org/ 38 | - run: npm ci 39 | - run: npm publish --access public --workspaces 40 | env: 41 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # Dependencies 4 | node_modules 5 | .pnp 6 | .pnp.js 7 | 8 | # Local env files 9 | .env 10 | .env.local 11 | .env.development.local 12 | .env.test.local 13 | .env.production.local 14 | 15 | # Testing 16 | coverage 17 | 18 | # Debug 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | 23 | # Misc 24 | .DS_Store 25 | *.pem 26 | test-report.xml 27 | **/lib/ 28 | stats.html 29 | *.tgz -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v18 -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 epilot GmbH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Lambda Server-Timing Middleware 2 | 3 | Enables Lambdas to return responses with Server-Timing Header allowing to to pass request-specific timings from the backend to the browser. 4 | 5 | Allows a server to communicate performance metrics about the request-response cycle to the user agent. It also standardizes a JavaScript interface to enable applications to collect, process, and act on these metrics to optimize application delivery. 6 | 7 | ![banner](https://github.com/NishuGoel/lambda-server-timing/assets/26349046/0afba8b2-88fc-4f11-a648-ab1d943d4639) 8 | 9 | # Install 10 | 11 | ``` 12 | $ npm install lambda-server-timing 13 | ``` 14 | 15 | # Usage 16 | ``` 17 | $ withServerTimings() 18 | ``` 19 | 20 | This will attach a Server-Timing header to your response headers with the timings recorded for requests. 21 | 22 | The header looks like: 23 | ``` 24 | HTTP/1.1 200 OK 25 | 26 | Server-Timing: db;dur=53, app;dur=47.2 27 | ``` 28 | 29 | And once succesfully returned as a response header, it is shown in the Timings tab under Networks in the Chrome Devtools. 30 | The visual example of it looks like: 31 | 32 | ![Visual Server-Timing header](https://github.com/NishuGoel/svelte-i18next/assets/26349046/5009ec62-7fe8-429d-8a5b-f338ad28225e) 33 | 34 | This now enables developers to look throught the performance problems of their apps for not just the frontend and but also the backend. 35 | 36 | The package also exposes the timer methods `startTime` and `endTime` so you can measure your methods on a regular basis and improve accordingly. 37 | 38 | 39 | # Usage - startTime/endTime 40 | ``` 41 | $ startTime('abbr', "getAbbreviatedResponse"); 42 | $ getAbbreviatedResponse({ 43 | ... 44 | }); 45 | $ endTime('abbr', "getAbbreviatedResponse"); 46 | ``` -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import middy from "@middy/core"; 2 | interface ServerTimingOptions { 3 | enabled?: boolean; 4 | } 5 | /** 6 | * @returns a lambda middleware that adds a Server-Timing header to the response 7 | */ 8 | export declare const withServerTimings: (opts?: ServerTimingOptions) => { 9 | after: (handler: middy.Request) => void; 10 | }; 11 | /** 12 | * @param name 13 | * @param description 14 | * Records the start time of a metric 15 | */ 16 | export declare const startTime: (name: string, description?: string) => void; 17 | /** 18 | * @param name 19 | * @param description 20 | * 21 | * @returns TimeObject 22 | * Records the duration of a metric and sets a metric timing 23 | */ 24 | export declare const endTime: (name: string, description?: string) => void | Record; 25 | interface TimeObject { 26 | name: string; 27 | description?: string; 28 | value: [number, number]; 29 | } 30 | export declare const setMetric: ({ name, value, description }: TimeObject) => void; 31 | export {}; 32 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | exports.setMetric = exports.endTime = exports.startTime = exports.withServerTimings = void 0; 7 | const lambda_powertools_logger_1 = __importDefault(require("@dazn/lambda-powertools-logger")); 8 | /** 9 | * @returns a lambda middleware that adds a Server-Timing header to the response 10 | */ 11 | const withServerTimings = (opts) => { 12 | if (opts === null || opts === void 0 ? void 0 : opts.enabled) { 13 | return { 14 | // add Server-Timing header to response 15 | after: (handler) => { 16 | const response = handler.response; 17 | // get timings from request 18 | try { 19 | const headers = []; 20 | const timings = getServerTimingHeader(headers); 21 | response.headers = Object.assign(Object.assign({}, response.headers), { "Server-Timing": timings }); 22 | } 23 | catch (e) { 24 | lambda_powertools_logger_1.default.debug(`Error: Could not record server timings - ${e}`); 25 | } 26 | }, 27 | }; 28 | } 29 | else { 30 | return { 31 | after: () => { }, 32 | }; 33 | } 34 | }; 35 | exports.withServerTimings = withServerTimings; 36 | /** 37 | * @param name 38 | * @param description 39 | * Records the start time of a metric 40 | */ 41 | const startTime = (name, description) => { 42 | try { 43 | if (typeof name !== "string") { 44 | return lambda_powertools_logger_1.default.debug("Metric name is not string"); 45 | } 46 | timer(name, description); 47 | } 48 | catch (e) { 49 | lambda_powertools_logger_1.default.debug(`Error: Could not record start time ${name} - ${e}`); 50 | } 51 | }; 52 | exports.startTime = startTime; 53 | /** 54 | * @param name 55 | * @param description 56 | * 57 | * @returns TimeObject 58 | * Records the duration of a metric and sets a metric timing 59 | */ 60 | const endTime = (name, description) => { 61 | var _a; 62 | try { 63 | if (typeof name !== "string") { 64 | return lambda_powertools_logger_1.default.debug("Metric name is not string"); 65 | } 66 | const obj = timerEnd(name); 67 | if (!obj) { 68 | return; 69 | } 70 | (0, exports.setMetric)({ 71 | name: obj.name, 72 | description: (_a = obj.description) !== null && _a !== void 0 ? _a : description, 73 | value: obj.value, 74 | }); 75 | return obj; 76 | } 77 | catch (e) { 78 | lambda_powertools_logger_1.default.debug(`Error: Could not record end time for ${name} - ${e}`); 79 | } 80 | }; 81 | exports.endTime = endTime; 82 | const times = new Map(); 83 | const timer = (name, description) => { 84 | times.set(name, { 85 | name, 86 | description: description || "", 87 | startTime: process.hrtime(), 88 | }); 89 | }; 90 | const timerEnd = (name, description) => { 91 | const timeObj = times.get(name); 92 | if (!timeObj) { 93 | return lambda_powertools_logger_1.default.debug(`No such name ${name}`); 94 | } 95 | const duration = process.hrtime(timeObj.startTime); 96 | if (!timeObj.description) { 97 | timeObj.description = description; 98 | } 99 | const value = duration[0] * 1e3 + duration[1] * 1e-6; 100 | timeObj.value = value; 101 | times.delete(name); 102 | return timeObj; 103 | }; 104 | let tempHeaders = []; 105 | const setMetric = ({ name, value, description }) => { 106 | if (typeof name !== "string") { 107 | return lambda_powertools_logger_1.default.debug("1st argument name is not string"); 108 | } 109 | if (typeof value !== "number") { 110 | return lambda_powertools_logger_1.default.debug("2nd argument value is not number"); 111 | } 112 | const dur = value; 113 | const metric = typeof description !== "string" || !description 114 | ? `${name}; dur=${dur !== null && dur !== void 0 ? dur : 0}` 115 | : `${name}; dur=${dur}; desc="${description}"`; 116 | tempHeaders.push(metric); 117 | }; 118 | exports.setMetric = setMetric; 119 | const getServerTimingHeader = (headers) => { 120 | headers.push(tempHeaders === null || tempHeaders === void 0 ? void 0 : tempHeaders.toString()); 121 | tempHeaders = []; 122 | return headers === null || headers === void 0 ? void 0 : headers.toString(); 123 | }; 124 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["src/index.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "@jest/types"; 2 | 3 | const config: Config.InitialOptions = { 4 | preset: "ts-jest", 5 | testEnvironment: "node", 6 | testMatch: ["**/?(*.)+(spec|test).ts?(x)"], 7 | testPathIgnorePatterns: ["node_modules"], 8 | verbose: true, 9 | silent: true, 10 | }; 11 | 12 | export default config; 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lambda-server-timing", 3 | "version": "0.2.0", 4 | "license": "MIT", 5 | "author": "Nishu Goel ", 6 | "contributors": [ 7 | "Nishu Goel (https://github.com/NishuGoel)" 8 | ], 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/NishuGoel/lambda-server-timing.git#main" 12 | }, 13 | "keywords": [ 14 | "server-timing", 15 | "lambda", 16 | "serverless", 17 | "performance" 18 | ], 19 | "main": "index.js", 20 | "types": "index.d.ts", 21 | "engines": { 22 | "node": ">=14" 23 | }, 24 | "files": [ 25 | "*.js", 26 | "*.map", 27 | "*.d.ts", 28 | "!*.test.*", 29 | "README.md" 30 | ], 31 | "scripts": { 32 | "build:declarations": "tsc -p tsconfig.json", 33 | "build": "npm-run-all -p build:declarations test lint", 34 | "test": "jest --verbose --detectOpenHandles --forceExit --passWithNoTests", 35 | "lint": "eslint src --ext .ts" 36 | }, 37 | "devDependencies": { 38 | "concurrently": "^6.4.0", 39 | "eslint-config-prettier": "^8.3.0", 40 | "eslint-plugin-prettier": "^4.0.0", 41 | "eslintrc": "^1.0.6", 42 | "prettier": "^2.5.0", 43 | "@types/aws-lambda": "^8.10.130", 44 | "jest": "^29.7.0", 45 | "npm-run-all": "^4.1.5", 46 | "ts-jest": "^29.1.1", 47 | "ts-node": "^10.9.2", 48 | "tslib": "^2.6.2", 49 | "typescript": "^5.3.3", 50 | "@typescript-eslint/eslint-plugin": "^6.16.0" 51 | }, 52 | "dependencies": { 53 | "@dazn/lambda-powertools-logger": "^1.28.1", 54 | "@middy/core": "^5.1.0" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import Log from "@dazn/lambda-powertools-logger"; 2 | import middy from "@middy/core"; 3 | import type * as Lambda from "aws-lambda"; 4 | 5 | interface ServerTimingOptions { 6 | enabled?: boolean; 7 | } 8 | /** 9 | * @returns a lambda middleware that adds a Server-Timing header to the response 10 | */ 11 | export const withServerTimings = (opts?: ServerTimingOptions) => { 12 | if (opts?.enabled) { 13 | return { 14 | // add Server-Timing header to response 15 | after: (handler: middy.Request) => { 16 | const response = 17 | handler.response as Lambda.APIGatewayProxyStructuredResultV2; 18 | // get timings from request 19 | 20 | try { 21 | const headers: unknown[] = []; 22 | const timings = getServerTimingHeader(headers); 23 | 24 | response.headers = { 25 | ...response.headers, 26 | "Server-Timing": timings, 27 | }; 28 | } catch (e) { 29 | Log.debug(`Error: Could not record server timings - ${e}`); 30 | } 31 | }, 32 | }; 33 | } else { 34 | return { 35 | after: () => {}, 36 | }; 37 | } 38 | }; 39 | 40 | /** 41 | * @param name 42 | * @param description 43 | * Records the start time of a metric 44 | */ 45 | export const startTime = (name: string, description?: string) => { 46 | try { 47 | if (typeof name !== "string") { 48 | return Log.debug("Metric name is not string"); 49 | } 50 | 51 | timer(name, description); 52 | } catch (e) { 53 | Log.debug(`Error: Could not record start time ${name} - ${e}`); 54 | } 55 | }; 56 | 57 | /** 58 | * @param name 59 | * @param description 60 | * 61 | * @returns TimeObject 62 | * Records the duration of a metric and sets a metric timing 63 | */ 64 | export const endTime = (name: string, description?: string) => { 65 | try { 66 | if (typeof name !== "string") { 67 | return Log.debug("Metric name is not string"); 68 | } 69 | 70 | const obj = timerEnd(name); 71 | if (!obj) { 72 | return; 73 | } 74 | setMetric({ 75 | name: obj.name as string, 76 | description: (obj.description as string) ?? description, 77 | value: obj.value as [number, number], 78 | }); 79 | 80 | return obj; 81 | } catch (e) { 82 | Log.debug(`Error: Could not record end time for ${name} - ${e}`); 83 | } 84 | }; 85 | 86 | interface TimeObject { 87 | name: string; 88 | description?: string; 89 | value: [number, number]; 90 | } 91 | const times = new Map>(); 92 | const timer = (name: string, description?: string) => { 93 | times.set(name, { 94 | name, 95 | description: description || "", 96 | startTime: process.hrtime(), 97 | }); 98 | }; 99 | 100 | const timerEnd = (name: string, description?: string) => { 101 | const timeObj = times.get(name); 102 | if (!timeObj) { 103 | return Log.debug(`No such name ${name}`); 104 | } 105 | const duration = process.hrtime(timeObj.startTime as [number, number]); 106 | if (!timeObj.description) { 107 | timeObj.description = description; 108 | } 109 | const value = duration[0] * 1e3 + duration[1] * 1e-6; 110 | timeObj.value = value; 111 | times.delete(name); 112 | 113 | return timeObj; 114 | }; 115 | 116 | let tempHeaders: unknown[] = []; 117 | 118 | export const setMetric = ({ name, value, description }: TimeObject) => { 119 | if (typeof name !== "string") { 120 | return Log.debug("1st argument name is not string"); 121 | } 122 | if (typeof value !== "number") { 123 | return Log.debug("2nd argument value is not number"); 124 | } 125 | 126 | const dur = value; 127 | 128 | const metric = 129 | typeof description !== "string" || !description 130 | ? `${name}; dur=${dur ?? 0}` 131 | : `${name}; dur=${dur}; desc="${description}"`; 132 | 133 | tempHeaders.push(metric); 134 | }; 135 | 136 | const getServerTimingHeader = (headers: unknown[]) => { 137 | headers.push(tempHeaders?.toString()); 138 | tempHeaders = []; 139 | 140 | return headers?.toString(); 141 | }; 142 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2016", 4 | "module": "commonjs", 5 | "declaration": true, 6 | "sourceMap": true, 7 | "rootDir": "src", 8 | "outDir": ".", 9 | "esModuleInterop": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "strict": true, 12 | "skipLibCheck": true 13 | }, 14 | "include": ["src/**/*"], 15 | "exclude": [] 16 | } 17 | --------------------------------------------------------------------------------