From 4e016b2acc583c8d8be511c1fd61c172f479ae85 Mon Sep 17 00:00:00 2001 From: jlenon7 Date: Wed, 23 Jul 2025 11:57:38 -0300 Subject: [PATCH 1/3] feat: implement loki driver --- .github/workflows/ci.yml | 7 + .gitignore | 3 + docker-compose.yml | 27 +++ loki-config.yaml | 35 ++++ package-lock.json | 237 ++++++++++++++++----------- package.json | 4 +- src/drivers/LokiDriver.ts | 41 +++++ src/factories/DriverFactory.ts | 2 + src/formatters/MessageFormatter.ts | 2 +- tests/fixtures/config/logging.ts | 6 + tests/unit/drivers/LokiDriverTest.ts | 51 ++++++ 11 files changed, 317 insertions(+), 98 deletions(-) create mode 100644 docker-compose.yml create mode 100644 loki-config.yaml create mode 100644 src/drivers/LokiDriver.ts create mode 100644 tests/unit/drivers/LokiDriverTest.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b2ad10b..e4a4de8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,13 @@ on: jobs: linux: runs-on: ubuntu-latest + services: + athenna_loki: + image: grafana/loki:2.9.1 + volumes: + - ./loki-config.yaml:/etc/loki/loki-config.yaml + ports: + - '3100:3100' strategy: matrix: node-version: diff --git a/.gitignore b/.gitignore index 6b64c32..0cd56a1 100644 --- a/.gitignore +++ b/.gitignore @@ -136,3 +136,6 @@ build # MacOS folder mapper file .DS_Store + +# Docker volumes +.docker diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..494aeda --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,27 @@ +version: '3' + +services: + athenna_loki: + image: grafana/loki:2.9.1 + ports: + - "3100:3100" + volumes: + - .docker/loki_data:/tmp/loki + - ./loki-config.yaml:/etc/loki/loki-config.yaml + command: + - "-config.file=/etc/loki/loki-config.yaml" + + athenna_grafana: + image: grafana/grafana:9.3.2 + ports: + - "3000:3000" + depends_on: + - athenna_loki + environment: + GF_SECURITY_ADMIN_PASSWORD: "admin" + volumes: + - grafana-storage:/var/lib/grafana + +volumes: + loki-data: + grafana-storage: diff --git a/loki-config.yaml b/loki-config.yaml new file mode 100644 index 0000000..69cfb0b --- /dev/null +++ b/loki-config.yaml @@ -0,0 +1,35 @@ +auth_enabled: false + +server: + http_listen_port: 3100 + grpc_listen_port: 9096 + log_level: debug + +common: + instance_addr: 127.0.0.1 + path_prefix: /tmp/loki + storage: + filesystem: + chunks_directory: /tmp/loki/chunks + rules_directory: /tmp/loki/rules + replication_factor: 1 + ring: + kvstore: + store: inmemory + +query_range: + results_cache: + cache: + embedded_cache: + enabled: true + max_size_mb: 100 + +schema_config: + configs: + - from: 2020-10-24 + store: tsdb + object_store: filesystem + schema: v13 + index: + prefix: index_ + period: 24h diff --git a/package-lock.json b/package-lock.json index 04f4ed6..b5d4367 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@athenna/logger", - "version": "5.3.0", + "version": "5.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@athenna/logger", - "version": "5.3.0", + "version": "5.4.0", "license": "MIT", "dependencies": { "@aws-lambda-powertools/logger": "^1.18.1", @@ -14,7 +14,7 @@ "telegraf": "^4.16.3" }, "devDependencies": { - "@athenna/common": "^5.7.0", + "@athenna/common": "^5.11.0", "@athenna/config": "^5.3.0", "@athenna/ioc": "^5.1.0", "@athenna/test": "^5.3.0", @@ -39,27 +39,28 @@ } }, "node_modules/@athenna/common": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@athenna/common/-/common-5.7.0.tgz", - "integrity": "sha512-mBWX6U7aW0n2LHMvEj0jS4iu0d9BVVTSGo29HUMfCFe05ceW3qRzKxvVIAArAxFKD8iFfkBnGXNOs5eU7w/9Vw==", + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@athenna/common/-/common-5.11.0.tgz", + "integrity": "sha512-MDwVrVXcWPrF7G5n0whLJSTbMiTDT0g8QjpGDDTq/uqMo6lqcYfqF78aHQAnigH8SNHCh6/LBvp+TyjoP5mN9A==", "dev": true, + "license": "MIT", "dependencies": { "@fastify/formbody": "^8.0.2", + "@humanwhocodes/retry": "^0.4.3", "bytes": "^3.1.2", "callsite": "^1.0.0", "chalk": "^5.4.1", "change-case": "^4.1.2", "collect.js": "^4.36.1", "crc": "^4.3.2", - "csv-parser": "^3.1.0", - "deasync": "^0.1.30", + "csv-parser": "^3.2.0", "execa": "^8.0.1", - "fastify": "^5.2.1", + "fastify": "^5.3.2", "got": "^12.6.1", "http-status-codes": "^2.3.0", "is-wsl": "^2.2.0", "js-yaml": "^4.1.0", - "json-2-csv": "^5.5.7", + "json-2-csv": "^5.5.8", "kind-of": "^6.0.3", "lodash": "^4.17.21", "mime-types": "^2.1.35", @@ -69,7 +70,7 @@ "parent-module": "^3.1.0", "pluralize": "^8.0.0", "prepend-file": "^2.0.1", - "ulid": "^2.3.0", + "ulid": "^2.4.0", "uuid": "^8.3.2", "validator-brazil": "^1.2.2", "youch": "^3.3.4", @@ -447,6 +448,7 @@ "url": "https://opencollective.com/fastify" } ], + "license": "MIT", "dependencies": { "ajv": "^8.12.0", "ajv-formats": "^3.0.1", @@ -454,15 +456,26 @@ } }, "node_modules/@fastify/error": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@fastify/error/-/error-4.0.0.tgz", - "integrity": "sha512-OO/SA8As24JtT1usTUTKgGH7uLvhfwZPwlptRi2Dp5P4KKmJI3gvsZ8MIHnNwDs4sLf/aai5LzTyl66xr7qMxA==", - "dev": true + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-4.2.0.tgz", + "integrity": "sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" }, "node_modules/@fastify/fast-json-stringify-compiler": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.0.2.tgz", - "integrity": "sha512-YdR7gqlLg1xZAQa+SX4sMNzQHY5pC54fu9oC5aYSUqBhyn6fkLkrdtKlpVdCNPlwuUuXA1PjFTEmvMF6ZVXVGw==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.0.3.tgz", + "integrity": "sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ==", "dev": true, "funding": [ { @@ -474,6 +487,7 @@ "url": "https://opencollective.com/fastify" } ], + "license": "MIT", "dependencies": { "fast-json-stringify": "^6.0.0" } @@ -502,7 +516,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@fastify/forwarded/-/forwarded-3.0.0.tgz", "integrity": "sha512-kJExsp4JCms7ipzg7SJ3y8DwmePaELHxKYtg+tZow+k0znUTf3cb+npgyqm8+ATZOdmfgfydIebPDWM172wfyA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@fastify/merge-json-schemas": { "version": "0.2.1", @@ -519,6 +534,7 @@ "url": "https://opencollective.com/fastify" } ], + "license": "MIT", "dependencies": { "dequal": "^2.0.3" } @@ -528,6 +544,7 @@ "resolved": "https://registry.npmjs.org/@fastify/proxy-addr/-/proxy-addr-5.0.0.tgz", "integrity": "sha512-37qVVA1qZ5sgH7KpHkkC4z9SK6StIsIcOmpjvMPXNb3vx2GQxhZocogVYbr2PbbeLCQxYIPDok307xEvRZOzGA==", "dev": true, + "license": "MIT", "dependencies": { "@fastify/forwarded": "^3.0.0", "ipaddr.js": "^2.1.0" @@ -590,6 +607,20 @@ "deprecated": "Use @eslint/object-schema instead", "dev": true }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1316,7 +1347,8 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/acorn": { "version": "8.14.0", @@ -1405,6 +1437,7 @@ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^8.0.0" }, @@ -1640,6 +1673,7 @@ "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.0.0" } @@ -1664,6 +1698,7 @@ "resolved": "https://registry.npmjs.org/avvio/-/avvio-9.1.0.tgz", "integrity": "sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==", "dev": true, + "license": "MIT", "dependencies": { "@fastify/error": "^4.0.0", "fastq": "^1.17.1" @@ -1708,15 +1743,6 @@ } ] }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dev": true, - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -2438,6 +2464,7 @@ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" } @@ -2657,10 +2684,11 @@ } }, "node_modules/csv-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/csv-parser/-/csv-parser-3.1.0.tgz", - "integrity": "sha512-egOwFF+imkpAE0gTrbzdf7c322lonHAmLPT2Ou1b5lhTSeXyfEdaMBdWuVeUJ6fsYuR0/ENonFo16kEXWKOQFw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/csv-parser/-/csv-parser-3.2.0.tgz", + "integrity": "sha512-fgKbp+AJbn1h2dcAHKIdKNSSjfp43BZZykXsCjzALjKy80VXQNHPFJ6T9Afwdzoj24aMkq8GwDS7KGcDPpejrA==", "dev": true, + "license": "MIT", "bin": { "csv-parser": "bin/csv-parser" }, @@ -2792,20 +2820,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/deasync": { - "version": "0.1.30", - "resolved": "https://registry.npmjs.org/deasync/-/deasync-0.1.30.tgz", - "integrity": "sha512-OaAjvEQuQ9tJsKG4oHO9nV1UHTwb2Qc2+fadB0VeVtD0Z9wiG1XPGLJ4W3aLhAoQSYTaLROFRbd5X20Dkzf7MQ==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "bindings": "^1.5.0", - "node-addon-api": "^1.7.1" - }, - "engines": { - "node": ">=0.11.0" - } - }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -2860,6 +2874,7 @@ "resolved": "https://registry.npmjs.org/deeks/-/deeks-3.1.0.tgz", "integrity": "sha512-e7oWH1LzIdv/prMQ7pmlDlaVoL64glqzvNgkgQNgyec9ORPHrT2jaOqMtRyqJuwWjtfb6v+2rk9pmaHj+F137A==", "dev": true, + "license": "MIT", "engines": { "node": ">= 16" } @@ -2939,6 +2954,7 @@ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -2984,6 +3000,7 @@ "resolved": "https://registry.npmjs.org/doc-path/-/doc-path-4.1.1.tgz", "integrity": "sha512-h1ErTglQAVv2gCnOpD3sFS6uolDbOKHDU1BZq+Kl3npPqroU3dYL42lUgMfd5UimlwtRgp7C9dLGwqQ5D2HYgQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=16" } @@ -3976,6 +3993,7 @@ "url": "https://opencollective.com/fastify" } ], + "license": "MIT", "dependencies": { "@fastify/merge-json-schemas": "^0.2.0", "ajv": "^8.12.0", @@ -4005,6 +4023,7 @@ "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -4026,9 +4045,9 @@ ] }, "node_modules/fastify": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.2.1.tgz", - "integrity": "sha512-rslrNBF67eg8/Gyn7P2URV8/6pz8kSAscFL4EThZJ8JBMaXacVdVE4hmUcnPNKERl5o/xTiBSLfdowBRhVF1WA==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.4.0.tgz", + "integrity": "sha512-I4dVlUe+WNQAhKSyv15w+dwUh2EPiEl4X2lGYMmNSgF83WzTMAPKGdWEv5tPsCQOb+SOZwz8Vlta2vF+OeDgRw==", "dev": true, "funding": [ { @@ -4040,6 +4059,7 @@ "url": "https://opencollective.com/fastify" } ], + "license": "MIT", "dependencies": { "@fastify/ajv-compiler": "^4.0.0", "@fastify/error": "^4.0.0", @@ -4051,9 +4071,9 @@ "find-my-way": "^9.0.0", "light-my-request": "^6.0.0", "pino": "^9.0.0", - "process-warning": "^4.0.0", + "process-warning": "^5.0.0", "rfdc": "^1.3.1", - "secure-json-parse": "^3.0.1", + "secure-json-parse": "^4.0.0", "semver": "^7.6.0", "toad-cache": "^3.7.0" } @@ -4109,12 +4129,6 @@ "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -4144,17 +4158,18 @@ } }, "node_modules/find-my-way": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.1.0.tgz", - "integrity": "sha512-Y5jIsuYR4BwWDYYQ2A/RWWE6gD8a0FMgtU+HOq1WKku+Cwdz8M1v8wcAmRXXM1/iqtoqg06v+LjAxMYbCjViMw==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.3.0.tgz", + "integrity": "sha512-eRoFWQw+Yv2tuYlK2pjFS2jGXSxSppAs3hSQjfxVKxM5amECzIgYYc1FEI8ZmhSh/Ig+FrKEz43NLRKJjYCZVg==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", "fast-querystring": "^1.0.0", - "safe-regex2": "^4.0.0" + "safe-regex2": "^5.0.0" }, "engines": { - "node": ">=14" + "node": ">=20" } }, "node_modules/find-node-modules": { @@ -5445,6 +5460,7 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 10" } @@ -6120,10 +6136,11 @@ } }, "node_modules/json-2-csv": { - "version": "5.5.7", - "resolved": "https://registry.npmjs.org/json-2-csv/-/json-2-csv-5.5.7.tgz", - "integrity": "sha512-aZ0EOadeNnO4ifF60oXXTH8P177WeHhFLbRLqILW1Kk1gNHlgAOuvddMwEIaxbLpCzx+vXo49whK6AILdg8qLg==", + "version": "5.5.9", + "resolved": "https://registry.npmjs.org/json-2-csv/-/json-2-csv-5.5.9.tgz", + "integrity": "sha512-l4g6GZVHrsN+5SKkpOmGNSvho+saDZwXzj/xmcO0lJAgklzwsiqy70HS5tA9djcRvBEybZ9IF6R1MDFTEsaOGQ==", "dev": true, + "license": "MIT", "dependencies": { "deeks": "3.1.0", "doc-path": "4.1.1" @@ -6165,6 +6182,7 @@ "url": "https://opencollective.com/fastify" } ], + "license": "MIT", "dependencies": { "dequal": "^2.0.3" } @@ -6252,9 +6270,9 @@ } }, "node_modules/light-my-request": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-6.5.1.tgz", - "integrity": "sha512-0q82RyxIextuDtkA0UDofhPHIiQ2kmpa7fwElCSlm/8nQl36cDU1Cw+CAO90Es0lReH2HChClKL84I86Nc52hg==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-6.6.0.tgz", + "integrity": "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==", "dev": true, "funding": [ { @@ -6266,12 +6284,30 @@ "url": "https://opencollective.com/fastify" } ], + "license": "BSD-3-Clause", "dependencies": { "cookie": "^1.0.1", "process-warning": "^4.0.0", "set-cookie-parser": "^2.6.0" } }, + "node_modules/light-my-request/node_modules/process-warning": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.1.tgz", + "integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, "node_modules/lilconfig": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.5.tgz", @@ -7250,12 +7286,6 @@ "tslib": "^2.0.3" } }, - "node_modules/node-addon-api": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", - "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==", - "dev": true - }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -7450,6 +7480,7 @@ "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.0.0" } @@ -7888,17 +7919,18 @@ } }, "node_modules/pino": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-9.6.0.tgz", - "integrity": "sha512-i85pKRCt4qMjZ1+L7sy2Ag4t1atFcdbEt76+7iRJn1g2BvsnRMGu9p8pivl9fs63M2kF/A0OacFZhTub+m/qMg==", + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.7.0.tgz", + "integrity": "sha512-vnMCM6xZTb1WDmLvtG2lE/2p+t9hDEIvTWJsu6FejkE62vB7gDhvzrpFR4Cw2to+9JNQxVnkAKVPA1KPB98vWg==", "dev": true, + "license": "MIT", "dependencies": { "atomic-sleep": "^1.0.0", "fast-redact": "^3.1.1", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^2.0.0", "pino-std-serializers": "^7.0.0", - "process-warning": "^4.0.0", + "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", @@ -7914,6 +7946,7 @@ "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", "dev": true, + "license": "MIT", "dependencies": { "split2": "^4.0.0" } @@ -7922,7 +7955,8 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/pkg-dir": { "version": "7.0.0", @@ -8132,9 +8166,9 @@ "dev": true }, "node_modules/process-warning": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.1.tgz", - "integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", "dev": true, "funding": [ { @@ -8145,7 +8179,8 @@ "type": "opencollective", "url": "https://opencollective.com/fastify" } - ] + ], + "license": "MIT" }, "node_modules/pump": { "version": "3.0.2", @@ -8190,7 +8225,8 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/quick-lru": { "version": "5.1.1", @@ -8251,6 +8287,7 @@ "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 12.13.0" } @@ -8445,6 +8482,7 @@ "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz", "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" } @@ -8670,9 +8708,9 @@ } }, "node_modules/safe-regex2": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-4.0.1.tgz", - "integrity": "sha512-goqsB+bSlOmVX+CiFX2PFc1OV88j5jvBqIM+DgqrucHnUguAUNtiNOs+aTadq2NqsLQ+TQ3UEVG3gtSFcdlkCg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.0.0.tgz", + "integrity": "sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw==", "dev": true, "funding": [ { @@ -8684,6 +8722,7 @@ "url": "https://opencollective.com/fastify" } ], + "license": "MIT", "dependencies": { "ret": "~0.5.0" } @@ -8693,6 +8732,7 @@ "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" } @@ -8712,9 +8752,9 @@ } }, "node_modules/secure-json-parse": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-3.0.2.tgz", - "integrity": "sha512-H6nS2o8bWfpFEV6U38sOSjS7bTbdgbCGU9wEM6W14P5H0QOsz94KCusifV44GpHDTu2nqZbuDNhTzu+mjDSw1w==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.0.0.tgz", + "integrity": "sha512-dxtLJO6sc35jWidmLxo7ij+Eg48PM/kleBsxpC8QJE0qJICe+KawkDQmvCMZUr9u7WKVHgMW6vy3fQ7zMiFZMA==", "dev": true, "funding": [ { @@ -8725,7 +8765,8 @@ "type": "opencollective", "url": "https://opencollective.com/fastify" } - ] + ], + "license": "BSD-3-Clause" }, "node_modules/semver": { "version": "7.6.3", @@ -8787,7 +8828,8 @@ "version": "2.7.1", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/set-function-length": { "version": "1.2.2", @@ -9034,6 +9076,7 @@ "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", "dev": true, + "license": "MIT", "dependencies": { "atomic-sleep": "^1.0.0" } @@ -9093,6 +9136,7 @@ "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", "dev": true, + "license": "ISC", "engines": { "node": ">= 10.x" } @@ -9522,6 +9566,7 @@ "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", "dev": true, + "license": "MIT", "dependencies": { "real-require": "^0.2.0" } @@ -9622,6 +9667,7 @@ "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.0.tgz", "integrity": "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" } @@ -9855,10 +9901,11 @@ } }, "node_modules/ulid": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/ulid/-/ulid-2.3.0.tgz", - "integrity": "sha512-keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ulid/-/ulid-2.4.0.tgz", + "integrity": "sha512-fIRiVTJNcSRmXKPZtGzFQv9WRrZ3M9eoptl/teFJvjOzmpU+/K/JH6HZ8deBfb5vMEpicJcLn7JmvdknlMq7Zg==", "dev": true, + "license": "MIT", "bin": { "ulid": "bin/cli.js" } diff --git a/package.json b/package.json index f712620..89d57e4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@athenna/logger", - "version": "5.3.0", + "version": "5.4.0", "description": "The Athenna logging solution. Log in stdout, files and buckets.", "license": "MIT", "author": "João Lenon ", @@ -66,7 +66,7 @@ "telegraf": "^4.16.3" }, "devDependencies": { - "@athenna/common": "^5.7.0", + "@athenna/common": "^5.11.0", "@athenna/config": "^5.3.0", "@athenna/ioc": "^5.1.0", "@athenna/test": "^5.3.0", diff --git a/src/drivers/LokiDriver.ts b/src/drivers/LokiDriver.ts new file mode 100644 index 0000000..052c4d9 --- /dev/null +++ b/src/drivers/LokiDriver.ts @@ -0,0 +1,41 @@ +/** + * @athenna/logger + * + * (c) João Lenon + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { debug } from '#src/debug' +import { Driver } from '#src/drivers/Driver' +import { HttpClient } from '@athenna/common' + +export class LokiDriver extends Driver { + public async transport(level: string, message: any): Promise { + if (!this.couldBeTransported(level)) { + return + } + + const timestamp = Date.now() * 1e6 + const formatted = this.format(level, message, true) + + const body = { + streams: [ + { + stream: { job: this.configs.job }, + values: [[timestamp.toString(), formatted]] + } + ] + } + + debug( + '[%s] Transporting logs with job %s and in url %s.', + LokiDriver.name, + this.configs.job, + this.configs.url + ) + + return HttpClient.builder(true).body(body).post(this.configs.url) + } +} diff --git a/src/factories/DriverFactory.ts b/src/factories/DriverFactory.ts index 60c7e41..2de9413 100644 --- a/src/factories/DriverFactory.ts +++ b/src/factories/DriverFactory.ts @@ -12,6 +12,7 @@ import { Options } from '@athenna/common' import { Driver } from '#src/drivers/Driver' import { FileDriver } from '#src/drivers/FileDriver' import { NullDriver } from '#src/drivers/NullDriver' +import { LokiDriver } from '#src/drivers/LokiDriver' import { SlackDriver } from '#src/drivers/SlackDriver' import { StackDriver } from '#src/drivers/StackDriver' import { LambdaDriver } from '#src/drivers/LambdaDriver' @@ -30,6 +31,7 @@ export class DriverFactory { public static drivers: Map = new Map() .set('file', { Driver: FileDriver }) .set('null', { Driver: NullDriver }) + .set('loki', { Driver: LokiDriver }) .set('slack', { Driver: SlackDriver }) .set('stack', { Driver: StackDriver }) .set('lambda', { Driver: LambdaDriver }) diff --git a/src/formatters/MessageFormatter.ts b/src/formatters/MessageFormatter.ts index de98512..55db57a 100644 --- a/src/formatters/MessageFormatter.ts +++ b/src/formatters/MessageFormatter.ts @@ -10,7 +10,7 @@ import { Formatter } from '#src/formatters/Formatter' export class MessageFormatter extends Formatter { - public format(message: string): string { + public format(message: string) { return this.clean( `${this.messageLevel()} - (${this.pid()}) - (${this.hostname()}): ${this.toString( message diff --git a/tests/fixtures/config/logging.ts b/tests/fixtures/config/logging.ts index 2dab208..190f08f 100644 --- a/tests/fixtures/config/logging.ts +++ b/tests/fixtures/config/logging.ts @@ -52,6 +52,12 @@ export default { driver: 'lambda', formatter: 'json' }, + loki: { + driver: 'loki', + formatter: 'json', + url: 'http://localhost:3100/loki/api/v1/push', + job: 'athenna' + }, request: { driver: 'console', formatter: 'request', diff --git a/tests/unit/drivers/LokiDriverTest.ts b/tests/unit/drivers/LokiDriverTest.ts new file mode 100644 index 0000000..39c1f4b --- /dev/null +++ b/tests/unit/drivers/LokiDriverTest.ts @@ -0,0 +1,51 @@ +/** + * @athenna/logger + * + * (c) João Lenon + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { Path } from '@athenna/common' +import { Config } from '@athenna/config' +import { Log, LoggerProvider } from '#src' +import { Test, AfterEach, BeforeEach, type Context } from '@athenna/test' + +export default class LokiDriverTest { + @BeforeEach() + public async beforeEach() { + await Config.loadAll(Path.fixtures('config')) + + new LoggerProvider().register() + } + + @AfterEach() + public async afterEach() { + Config.clear() + } + + @Test() + public async shouldBeAbleToLogInLoki({ assert }: Context) { + const log = Log.config({ level: 'error' }).channel('loki') + + const message = 'hello' + + const [traceRes] = await log.trace(message) + const [debugRes] = await log.debug(message) + const [infoRes] = await log.info(message) + const [successRes] = await log.success({ hello: 'world!' }) + const [warnRes] = await log.warn(message) + const [errorRes] = await log.error({ hello: 'world!' }) + const [fatalRes] = await log.fatal(message) + + assert.isUndefined(traceRes) + assert.isUndefined(debugRes) + assert.isUndefined(infoRes) + assert.isUndefined(successRes) + assert.isUndefined(warnRes) + + assert.equal(errorRes.statusCode, 204) + assert.equal(fatalRes.statusCode, 204) + } +} From 683b435434122168cc0375b554aef3bced10605f Mon Sep 17 00:00:00 2001 From: jlenon7 Date: Wed, 23 Jul 2025 11:59:59 -0300 Subject: [PATCH 2/3] test: add new driver to available drivers list --- tests/unit/factories/DriverFactoryTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/factories/DriverFactoryTest.ts b/tests/unit/factories/DriverFactoryTest.ts index cfc8a4a..a2a5249 100644 --- a/tests/unit/factories/DriverFactoryTest.ts +++ b/tests/unit/factories/DriverFactoryTest.ts @@ -38,7 +38,7 @@ export default class DriverFactoryTest { public async shouldBeAbleToListAllAvailableDrivers({ assert }: Context) { const drivers = DriverFactory.availableDrivers() - assert.deepEqual(drivers, ['file', 'null', 'slack', 'stack', 'lambda', 'console', 'discord', 'telegram']) + assert.deepEqual(drivers, ['file', 'null', 'loki', 'slack', 'stack', 'lambda', 'console', 'discord', 'telegram']) } @Test() From 5d00b1e31b661fd28833a1efe737fc816528e7d1 Mon Sep 17 00:00:00 2001 From: jlenon7 Date: Wed, 23 Jul 2025 12:04:01 -0300 Subject: [PATCH 3/3] test: skip loki test on windows --- tests/unit/drivers/LokiDriverTest.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/unit/drivers/LokiDriverTest.ts b/tests/unit/drivers/LokiDriverTest.ts index 39c1f4b..007d308 100644 --- a/tests/unit/drivers/LokiDriverTest.ts +++ b/tests/unit/drivers/LokiDriverTest.ts @@ -7,7 +7,7 @@ * file that was distributed with this source code. */ -import { Path } from '@athenna/common' +import { Is, Path } from '@athenna/common' import { Config } from '@athenna/config' import { Log, LoggerProvider } from '#src' import { Test, AfterEach, BeforeEach, type Context } from '@athenna/test' @@ -27,6 +27,14 @@ export default class LokiDriverTest { @Test() public async shouldBeAbleToLogInLoki({ assert }: Context) { + /** + * Running Loki container instance is not supported on + * Windows CI of GitHub Actions. + */ + if (Is.Windows()) { + return + } + const log = Log.config({ level: 'error' }).channel('loki') const message = 'hello'