From e36a093a5c7c6dc3d55d3e2b868fd2505fe40281 Mon Sep 17 00:00:00 2001 From: Dejmenek Date: Fri, 15 May 2026 17:35:25 +0200 Subject: [PATCH 1/5] docs: add Loki logging to observability stack Expanded README to include Loki as part of the observability stack alongside Prometheus and Grafana. Clarified that the API exports logs to Loki via Serilog's OpenTelemetry sink. Updated Docker Compose service list and Grafana setup instructions to include Loki as a data source. --- README.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 55b423c..e4d7709 100644 --- a/README.md +++ b/README.md @@ -43,11 +43,11 @@ ### Prerequisites - [.NET 9 SDK](https://dotnet.microsoft.com/download/dotnet/9.0) -- [Docker](https://www.docker.com/) (for Prometheus and Grafana) +- [Docker](https://www.docker.com/) (for Prometheus, Loki, and Grafana) -### Start Prometheus and Grafana +### Start the Observability Stack -The API exports metrics via OpenTelemetry's OTLP exporter directly to Prometheus. Before starting the API, bring up the observability stack with Docker Compose: +The API exports metrics via OpenTelemetry's OTLP exporter to Prometheus and ships structured logs via Serilog's OpenTelemetry sink to Loki. Before starting the API, bring up the observability stack with Docker Compose: ```bash docker-compose up -d @@ -58,9 +58,11 @@ This starts: | Service | URL | | --- | --- | | Prometheus | http://localhost:5431 | +| Loki | http://localhost:3100 | | Grafana | http://localhost:3000 | -Prometheus is configured with `--web.enable-otlp-receiver` so it accepts OTLP pushes from the API. The scrape interval is set to 15 s globally (10 s for the Prometheus self-scrape job). +- **Prometheus** is configured with `--web.enable-otlp-receiver` so it accepts OTLP pushes from the API. The scrape interval is set to 15 s globally (10 s for the Prometheus self-scrape job). +- **Loki** listens on port `3100` and accepts logs over the OTLP HTTP endpoint (`/otlp`). Logs are stored on the local filesystem. ### Start the API @@ -68,11 +70,16 @@ Prometheus is configured with `--web.enable-otlp-receiver` so it accepts OTLP pu dotnet run --project TournamentAPI ``` -The API will push metrics to Prometheus automatically once running. +The API will push metrics to Prometheus and logs to Loki automatically once running. ### Grafana -Open `http://localhost:3000`, log in with the default credentials (`admin` / `admin`), and add Prometheus (`http://prometheus:9090`) as a data source to build dashboards from the collected metrics. +Open `http://localhost:3000`, log in with the default credentials (`admin` / `admin`), and add the following data sources to build dashboards: + +| Data source | URL | +| --- | --- | +| Prometheus | `http://prometheus:9090` | +| Loki | `http://loki:3100` | --- From 5596979109709f41ebc28862d5f2a9f43cd49996 Mon Sep 17 00:00:00 2001 From: Dejmenek Date: Fri, 15 May 2026 17:35:43 +0200 Subject: [PATCH 2/5] feat: add Loki config for local filesystem storage Added a complete loki.yml for deploying Loki with filesystem-based storage. Config disables authentication, enables structured metadata, sets server to listen on port 3100, uses in-memory ring for single-instance mode, and configures TSDB schema with local directories for index and chunk storage under /tmp/loki. --- loki/loki.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 loki/loki.yml diff --git a/loki/loki.yml b/loki/loki.yml new file mode 100644 index 0000000..f9f6be8 --- /dev/null +++ b/loki/loki.yml @@ -0,0 +1,32 @@ +# This is a complete configuration to deploy Loki backed by the filesystem. +# The index will be shipped to the storage via tsdb-shipper. + +auth_enabled: false + +limits_config: + allow_structured_metadata: true + +server: + http_listen_port: 3100 + +common: + ring: + instance_addr: 127.0.0.1 + kvstore: + store: inmemory + replication_factor: 1 + path_prefix: /tmp/loki + +schema_config: + configs: + - from: 2020-05-15 + store: tsdb + object_store: filesystem + schema: v13 + index: + prefix: index_ + period: 24h + +storage_config: + filesystem: + directory: /tmp/loki/chunks \ No newline at end of file From 1c0cce77694ac2ebb33fa0ee19ad5e191dbd8757 Mon Sep 17 00:00:00 2001 From: Dejmenek Date: Fri, 15 May 2026 17:36:37 +0200 Subject: [PATCH 3/5] build: add Serilog.AspNetCore and OpenTelemetry sink packages Added Serilog.AspNetCore, Serilog.Extensions.Logging, and Serilog.Sinks.OpenTelemetry to project dependencies for enhanced logging and OpenTelemetry export support. Updated packages.lock.json with all required transitive dependencies. No packages were removed or downgraded. --- Directory.Packages.props | 3 + TournamentAPI.Benchmarks/packages.lock.json | 113 ++++++++++++++++- .../packages.lock.json | 113 ++++++++++++++++- TournamentAPI.LoadTests/packages.lock.json | 95 +++++++++++++- TournamentAPI.UnitTests/packages.lock.json | 118 +++++++++++++++++- TournamentAPI/TournamentAPI.csproj | 2 + TournamentAPI/packages.lock.json | 109 ++++++++++++++++ 7 files changed, 549 insertions(+), 4 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index f0caa74..f942bde 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -22,6 +22,8 @@ + + @@ -29,6 +31,7 @@ + diff --git a/TournamentAPI.Benchmarks/packages.lock.json b/TournamentAPI.Benchmarks/packages.lock.json index 612b164..a692237 100644 --- a/TournamentAPI.Benchmarks/packages.lock.json +++ b/TournamentAPI.Benchmarks/packages.lock.json @@ -91,6 +91,11 @@ "resolved": "2.3.0", "contentHash": "2ap/rYmjtzCOT8hxrnEW/QeiOt+paD8iRrIcdKX0cxVwWLFa1e+JDBNeECakmccXrSFeBQuu5AV8SNkipFMMMw==" }, + "Google.Protobuf": { + "type": "Transitive", + "resolved": "3.30.1", + "contentHash": "HeWXDQBabQn/sCGicbeLJ0HMunknfC4FdLrOQOsaMJHcpqx3HVIpyyJqTrqJlWnza870twhOb+rBcaTiC/TlNA==" + }, "GreenDonut": { "type": "Transitive", "resolved": "15.1.15", @@ -138,6 +143,28 @@ "resolved": "15.1.15", "contentHash": "zKYnY8NMsZvQSY3Mdwtkivu4K/uMCSSjAB9YDiXV54mDwehnYVlFzMTX9MzB34+f8menzcZ8Ko/tqzlhKSt8Sw==" }, + "Grpc.Core.Api": { + "type": "Transitive", + "resolved": "2.70.0", + "contentHash": "66UotvWcSIq41oiQhLWcQACyKPM4umxXNiht5DQTLZJfNwEswWOcS7Z0xIEHyNIBE7ZpjotH22bEjTkvhPxmVw==" + }, + "Grpc.Net.Client": { + "type": "Transitive", + "resolved": "2.70.0", + "contentHash": "xNv0FFCVJa5S1beUtye82WFCxKThuE1jbN8DO1x1Rj8VSIWXLBUmfSID5a1fGzsU2R/EMfwPoWclJ2RMfQuGXw==", + "dependencies": { + "Grpc.Net.Common": "2.70.0", + "Microsoft.Extensions.Logging.Abstractions": "6.0.0" + } + }, + "Grpc.Net.Common": { + "type": "Transitive", + "resolved": "2.70.0", + "contentHash": "rBdEUMyCwa+iB8mqC6JKyPbj3SBHHkReJj/yy/XKJI63GcG6w9DJMMGTVcYHqq4Ci2W4m0HT4jt2pFfFscar8g==", + "dependencies": { + "Grpc.Core.Api": "2.70.0" + } + }, "HotChocolate": { "type": "Transitive", "resolved": "15.1.15", @@ -1168,6 +1195,52 @@ "resolved": "3.2.4", "contentHash": "I5qFifWw/gaTQT52MhzjZpkm/JPlfjSeO/DTZJjO7+hTKI+0aGRgOgZ3NN6D96dDuuqbIAZSeA5RimtHjqrA2A==" }, + "Serilog.Extensions.Hosting": { + "type": "Transitive", + "resolved": "9.0.0", + "contentHash": "u2TRxuxbjvTAldQn7uaAwePkWxTHIqlgjelekBtilAGL5sYyF3+65NWctN4UrwwGLsDC7c3Vz3HnOlu+PcoxXg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0", + "Microsoft.Extensions.Hosting.Abstractions": "9.0.0", + "Microsoft.Extensions.Logging.Abstractions": "9.0.0", + "Serilog": "4.2.0", + "Serilog.Extensions.Logging": "9.0.0" + } + }, + "Serilog.Formatting.Compact": { + "type": "Transitive", + "resolved": "3.0.0", + "contentHash": "wQsv14w9cqlfB5FX2MZpNsTawckN4a8dryuNGbebB/3Nh1pXnROHZov3swtu3Nj5oNG7Ba+xdu7Et/ulAUPanQ==", + "dependencies": { + "Serilog": "4.0.0" + } + }, + "Serilog.Settings.Configuration": { + "type": "Transitive", + "resolved": "9.0.0", + "contentHash": "4/Et4Cqwa+F88l5SeFeNZ4c4Z6dEAIKbu3MaQb2Zz9F/g27T5a3wvfMcmCOaAiACjfUb4A6wrlTVfyYUZk3RRQ==", + "dependencies": { + "Microsoft.Extensions.Configuration.Binder": "9.0.0", + "Microsoft.Extensions.DependencyModel": "9.0.0", + "Serilog": "4.2.0" + } + }, + "Serilog.Sinks.Debug": { + "type": "Transitive", + "resolved": "3.0.0", + "contentHash": "4BzXcdrgRX7wde9PmHuYd9U6YqycCC28hhpKonK7hx0wb19eiuRj16fPcPSVp0o/Y1ipJuNLYQ00R3q2Zs8FDA==", + "dependencies": { + "Serilog": "4.0.0" + } + }, + "Serilog.Sinks.File": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "lxjg89Y8gJMmFxVkbZ+qDgjl+T4yC5F7WSLTvA+5q0R04tfKVLRL/EHpYoJ/MEQd2EeCKDuylBIVnAYMotmh2A==", + "dependencies": { + "Serilog": "4.0.0" + } + }, "System.Buffers": { "type": "Transitive", "resolved": "4.6.0", @@ -1361,7 +1434,9 @@ "OpenTelemetry.Instrumentation.AspNetCore": "[1.15.2, )", "OpenTelemetry.Instrumentation.Http": "[1.15.1, )", "Serilog": "[4.3.0, )", - "Serilog.Sinks.Console": "[6.1.1, )" + "Serilog.AspNetCore": "[9.0.0, )", + "Serilog.Sinks.Console": "[6.1.1, )", + "Serilog.Sinks.OpenTelemetry": "[4.2.0, )" } }, "tournamentapi.shared": { @@ -1557,6 +1632,31 @@ "resolved": "4.3.0", "contentHash": "+cDryFR0GRhsGOnZSKwaDzRRl4MupvJ42FhCE4zhQRVanX0Jpg6WuCBk59OVhVDPmab1bB+nRykAnykYELA9qQ==" }, + "Serilog.AspNetCore": { + "type": "CentralTransitive", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "JslDajPlBsn3Pww1554flJFTqROvK9zz9jONNQgn0D8Lx2Trw8L0A8/n6zEQK1DAZWXrJwiVLw8cnTR3YFuYsg==", + "dependencies": { + "Serilog": "4.2.0", + "Serilog.Extensions.Hosting": "9.0.0", + "Serilog.Formatting.Compact": "3.0.0", + "Serilog.Settings.Configuration": "9.0.0", + "Serilog.Sinks.Console": "6.0.0", + "Serilog.Sinks.Debug": "3.0.0", + "Serilog.Sinks.File": "6.0.0" + } + }, + "Serilog.Extensions.Logging": { + "type": "CentralTransitive", + "requested": "[9.0.2, )", + "resolved": "9.0.0", + "contentHash": "NwSSYqPJeKNzl5AuXVHpGbr6PkZJFlNa14CdIebVjK3k/76kYj/mz5kiTRNVSsSaxM8kAIa1kpy/qyT9E4npRQ==", + "dependencies": { + "Microsoft.Extensions.Logging": "9.0.0", + "Serilog": "4.2.0" + } + }, "Serilog.Sinks.Console": { "type": "CentralTransitive", "requested": "[6.1.1, )", @@ -1565,6 +1665,17 @@ "dependencies": { "Serilog": "4.0.0" } + }, + "Serilog.Sinks.OpenTelemetry": { + "type": "CentralTransitive", + "requested": "[4.2.0, )", + "resolved": "4.2.0", + "contentHash": "PzMCyE5G19tjr5IZEi5qg+4UU5QrxBEoBEMu/hhYybTrGKXqUDiSGWKZNUDBgelaVKqLADlsmlJVyKce5SyPrg==", + "dependencies": { + "Google.Protobuf": "3.30.1", + "Grpc.Net.Client": "2.70.0", + "Serilog": "4.2.0" + } } } } diff --git a/TournamentAPI.IntegrationTests/packages.lock.json b/TournamentAPI.IntegrationTests/packages.lock.json index a1f0d9f..5758e54 100644 --- a/TournamentAPI.IntegrationTests/packages.lock.json +++ b/TournamentAPI.IntegrationTests/packages.lock.json @@ -121,6 +121,11 @@ "Docker.DotNet.Enhanced": "3.130.0" } }, + "Google.Protobuf": { + "type": "Transitive", + "resolved": "3.30.1", + "contentHash": "HeWXDQBabQn/sCGicbeLJ0HMunknfC4FdLrOQOsaMJHcpqx3HVIpyyJqTrqJlWnza870twhOb+rBcaTiC/TlNA==" + }, "GreenDonut": { "type": "Transitive", "resolved": "15.1.15", @@ -168,6 +173,28 @@ "resolved": "15.1.15", "contentHash": "zKYnY8NMsZvQSY3Mdwtkivu4K/uMCSSjAB9YDiXV54mDwehnYVlFzMTX9MzB34+f8menzcZ8Ko/tqzlhKSt8Sw==" }, + "Grpc.Core.Api": { + "type": "Transitive", + "resolved": "2.70.0", + "contentHash": "66UotvWcSIq41oiQhLWcQACyKPM4umxXNiht5DQTLZJfNwEswWOcS7Z0xIEHyNIBE7ZpjotH22bEjTkvhPxmVw==" + }, + "Grpc.Net.Client": { + "type": "Transitive", + "resolved": "2.70.0", + "contentHash": "xNv0FFCVJa5S1beUtye82WFCxKThuE1jbN8DO1x1Rj8VSIWXLBUmfSID5a1fGzsU2R/EMfwPoWclJ2RMfQuGXw==", + "dependencies": { + "Grpc.Net.Common": "2.70.0", + "Microsoft.Extensions.Logging.Abstractions": "6.0.0" + } + }, + "Grpc.Net.Common": { + "type": "Transitive", + "resolved": "2.70.0", + "contentHash": "rBdEUMyCwa+iB8mqC6JKyPbj3SBHHkReJj/yy/XKJI63GcG6w9DJMMGTVcYHqq4Ci2W4m0HT4jt2pFfFscar8g==", + "dependencies": { + "Grpc.Core.Api": "2.70.0" + } + }, "HotChocolate": { "type": "Transitive", "resolved": "15.1.15", @@ -1146,6 +1173,52 @@ "OpenTelemetry.Api": "1.15.3" } }, + "Serilog.Extensions.Hosting": { + "type": "Transitive", + "resolved": "9.0.0", + "contentHash": "u2TRxuxbjvTAldQn7uaAwePkWxTHIqlgjelekBtilAGL5sYyF3+65NWctN4UrwwGLsDC7c3Vz3HnOlu+PcoxXg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0", + "Microsoft.Extensions.Hosting.Abstractions": "9.0.0", + "Microsoft.Extensions.Logging.Abstractions": "9.0.0", + "Serilog": "4.2.0", + "Serilog.Extensions.Logging": "9.0.0" + } + }, + "Serilog.Formatting.Compact": { + "type": "Transitive", + "resolved": "3.0.0", + "contentHash": "wQsv14w9cqlfB5FX2MZpNsTawckN4a8dryuNGbebB/3Nh1pXnROHZov3swtu3Nj5oNG7Ba+xdu7Et/ulAUPanQ==", + "dependencies": { + "Serilog": "4.0.0" + } + }, + "Serilog.Settings.Configuration": { + "type": "Transitive", + "resolved": "9.0.0", + "contentHash": "4/Et4Cqwa+F88l5SeFeNZ4c4Z6dEAIKbu3MaQb2Zz9F/g27T5a3wvfMcmCOaAiACjfUb4A6wrlTVfyYUZk3RRQ==", + "dependencies": { + "Microsoft.Extensions.Configuration.Binder": "9.0.0", + "Microsoft.Extensions.DependencyModel": "9.0.0", + "Serilog": "4.2.0" + } + }, + "Serilog.Sinks.Debug": { + "type": "Transitive", + "resolved": "3.0.0", + "contentHash": "4BzXcdrgRX7wde9PmHuYd9U6YqycCC28hhpKonK7hx0wb19eiuRj16fPcPSVp0o/Y1ipJuNLYQ00R3q2Zs8FDA==", + "dependencies": { + "Serilog": "4.0.0" + } + }, + "Serilog.Sinks.File": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "lxjg89Y8gJMmFxVkbZ+qDgjl+T4yC5F7WSLTvA+5q0R04tfKVLRL/EHpYoJ/MEQd2EeCKDuylBIVnAYMotmh2A==", + "dependencies": { + "Serilog": "4.0.0" + } + }, "SharpZipLib": { "type": "Transitive", "resolved": "1.4.2", @@ -1377,7 +1450,9 @@ "OpenTelemetry.Instrumentation.AspNetCore": "[1.15.2, )", "OpenTelemetry.Instrumentation.Http": "[1.15.1, )", "Serilog": "[4.3.0, )", - "Serilog.Sinks.Console": "[6.1.1, )" + "Serilog.AspNetCore": "[9.0.0, )", + "Serilog.Sinks.Console": "[6.1.1, )", + "Serilog.Sinks.OpenTelemetry": "[4.2.0, )" } }, "tournamentapi.shared": { @@ -1573,6 +1648,31 @@ "resolved": "4.3.0", "contentHash": "+cDryFR0GRhsGOnZSKwaDzRRl4MupvJ42FhCE4zhQRVanX0Jpg6WuCBk59OVhVDPmab1bB+nRykAnykYELA9qQ==" }, + "Serilog.AspNetCore": { + "type": "CentralTransitive", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "JslDajPlBsn3Pww1554flJFTqROvK9zz9jONNQgn0D8Lx2Trw8L0A8/n6zEQK1DAZWXrJwiVLw8cnTR3YFuYsg==", + "dependencies": { + "Serilog": "4.2.0", + "Serilog.Extensions.Hosting": "9.0.0", + "Serilog.Formatting.Compact": "3.0.0", + "Serilog.Settings.Configuration": "9.0.0", + "Serilog.Sinks.Console": "6.0.0", + "Serilog.Sinks.Debug": "3.0.0", + "Serilog.Sinks.File": "6.0.0" + } + }, + "Serilog.Extensions.Logging": { + "type": "CentralTransitive", + "requested": "[9.0.2, )", + "resolved": "9.0.0", + "contentHash": "NwSSYqPJeKNzl5AuXVHpGbr6PkZJFlNa14CdIebVjK3k/76kYj/mz5kiTRNVSsSaxM8kAIa1kpy/qyT9E4npRQ==", + "dependencies": { + "Microsoft.Extensions.Logging": "9.0.0", + "Serilog": "4.2.0" + } + }, "Serilog.Sinks.Console": { "type": "CentralTransitive", "requested": "[6.1.1, )", @@ -1581,6 +1681,17 @@ "dependencies": { "Serilog": "4.0.0" } + }, + "Serilog.Sinks.OpenTelemetry": { + "type": "CentralTransitive", + "requested": "[4.2.0, )", + "resolved": "4.2.0", + "contentHash": "PzMCyE5G19tjr5IZEi5qg+4UU5QrxBEoBEMu/hhYybTrGKXqUDiSGWKZNUDBgelaVKqLADlsmlJVyKce5SyPrg==", + "dependencies": { + "Google.Protobuf": "3.30.1", + "Grpc.Net.Client": "2.70.0", + "Serilog": "4.2.0" + } } } } diff --git a/TournamentAPI.LoadTests/packages.lock.json b/TournamentAPI.LoadTests/packages.lock.json index a73bd3a..31f6307 100644 --- a/TournamentAPI.LoadTests/packages.lock.json +++ b/TournamentAPI.LoadTests/packages.lock.json @@ -241,6 +241,11 @@ "FSharp.Core": "8.0.400" } }, + "Google.Protobuf": { + "type": "Transitive", + "resolved": "3.30.1", + "contentHash": "HeWXDQBabQn/sCGicbeLJ0HMunknfC4FdLrOQOsaMJHcpqx3HVIpyyJqTrqJlWnza870twhOb+rBcaTiC/TlNA==" + }, "GreenDonut": { "type": "Transitive", "resolved": "15.1.15", @@ -288,6 +293,28 @@ "resolved": "15.1.15", "contentHash": "zKYnY8NMsZvQSY3Mdwtkivu4K/uMCSSjAB9YDiXV54mDwehnYVlFzMTX9MzB34+f8menzcZ8Ko/tqzlhKSt8Sw==" }, + "Grpc.Core.Api": { + "type": "Transitive", + "resolved": "2.70.0", + "contentHash": "66UotvWcSIq41oiQhLWcQACyKPM4umxXNiht5DQTLZJfNwEswWOcS7Z0xIEHyNIBE7ZpjotH22bEjTkvhPxmVw==" + }, + "Grpc.Net.Client": { + "type": "Transitive", + "resolved": "2.70.0", + "contentHash": "xNv0FFCVJa5S1beUtye82WFCxKThuE1jbN8DO1x1Rj8VSIWXLBUmfSID5a1fGzsU2R/EMfwPoWclJ2RMfQuGXw==", + "dependencies": { + "Grpc.Net.Common": "2.70.0", + "Microsoft.Extensions.Logging.Abstractions": "6.0.0" + } + }, + "Grpc.Net.Common": { + "type": "Transitive", + "resolved": "2.70.0", + "contentHash": "rBdEUMyCwa+iB8mqC6JKyPbj3SBHHkReJj/yy/XKJI63GcG6w9DJMMGTVcYHqq4Ci2W4m0HT4jt2pFfFscar8g==", + "dependencies": { + "Grpc.Core.Api": "2.70.0" + } + }, "HdrHistogram": { "type": "Transitive", "resolved": "2.5.0", @@ -1532,6 +1559,26 @@ "Serilog": "4.0.0" } }, + "Serilog.Extensions.Hosting": { + "type": "Transitive", + "resolved": "9.0.0", + "contentHash": "u2TRxuxbjvTAldQn7uaAwePkWxTHIqlgjelekBtilAGL5sYyF3+65NWctN4UrwwGLsDC7c3Vz3HnOlu+PcoxXg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0", + "Microsoft.Extensions.Hosting.Abstractions": "9.0.0", + "Microsoft.Extensions.Logging.Abstractions": "9.0.0", + "Serilog": "4.2.0", + "Serilog.Extensions.Logging": "9.0.0" + } + }, + "Serilog.Formatting.Compact": { + "type": "Transitive", + "resolved": "3.0.0", + "contentHash": "wQsv14w9cqlfB5FX2MZpNsTawckN4a8dryuNGbebB/3Nh1pXnROHZov3swtu3Nj5oNG7Ba+xdu7Et/ulAUPanQ==", + "dependencies": { + "Serilog": "4.0.0" + } + }, "Serilog.Settings.Configuration": { "type": "Transitive", "resolved": "9.0.0", @@ -1542,6 +1589,14 @@ "Serilog": "4.2.0" } }, + "Serilog.Sinks.Debug": { + "type": "Transitive", + "resolved": "3.0.0", + "contentHash": "4BzXcdrgRX7wde9PmHuYd9U6YqycCC28hhpKonK7hx0wb19eiuRj16fPcPSVp0o/Y1ipJuNLYQ00R3q2Zs8FDA==", + "dependencies": { + "Serilog": "4.0.0" + } + }, "Serilog.Sinks.File": { "type": "Transitive", "resolved": "6.0.0", @@ -2504,7 +2559,9 @@ "OpenTelemetry.Instrumentation.AspNetCore": "[1.15.2, )", "OpenTelemetry.Instrumentation.Http": "[1.15.1, )", "Serilog": "[4.3.0, )", - "Serilog.Sinks.Console": "[6.1.1, )" + "Serilog.AspNetCore": "[9.0.0, )", + "Serilog.Sinks.Console": "[6.1.1, )", + "Serilog.Sinks.OpenTelemetry": "[4.2.0, )" } }, "tournamentapi.shared": { @@ -2700,6 +2757,31 @@ "resolved": "4.3.0", "contentHash": "+cDryFR0GRhsGOnZSKwaDzRRl4MupvJ42FhCE4zhQRVanX0Jpg6WuCBk59OVhVDPmab1bB+nRykAnykYELA9qQ==" }, + "Serilog.AspNetCore": { + "type": "CentralTransitive", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "JslDajPlBsn3Pww1554flJFTqROvK9zz9jONNQgn0D8Lx2Trw8L0A8/n6zEQK1DAZWXrJwiVLw8cnTR3YFuYsg==", + "dependencies": { + "Serilog": "4.2.0", + "Serilog.Extensions.Hosting": "9.0.0", + "Serilog.Formatting.Compact": "3.0.0", + "Serilog.Settings.Configuration": "9.0.0", + "Serilog.Sinks.Console": "6.0.0", + "Serilog.Sinks.Debug": "3.0.0", + "Serilog.Sinks.File": "6.0.0" + } + }, + "Serilog.Extensions.Logging": { + "type": "CentralTransitive", + "requested": "[9.0.2, )", + "resolved": "9.0.0", + "contentHash": "NwSSYqPJeKNzl5AuXVHpGbr6PkZJFlNa14CdIebVjK3k/76kYj/mz5kiTRNVSsSaxM8kAIa1kpy/qyT9E4npRQ==", + "dependencies": { + "Microsoft.Extensions.Logging": "9.0.0", + "Serilog": "4.2.0" + } + }, "Serilog.Sinks.Console": { "type": "CentralTransitive", "requested": "[6.1.1, )", @@ -2708,6 +2790,17 @@ "dependencies": { "Serilog": "4.0.0" } + }, + "Serilog.Sinks.OpenTelemetry": { + "type": "CentralTransitive", + "requested": "[4.2.0, )", + "resolved": "4.2.0", + "contentHash": "PzMCyE5G19tjr5IZEi5qg+4UU5QrxBEoBEMu/hhYybTrGKXqUDiSGWKZNUDBgelaVKqLADlsmlJVyKce5SyPrg==", + "dependencies": { + "Google.Protobuf": "3.30.1", + "Grpc.Net.Client": "2.70.0", + "Serilog": "4.2.0" + } } } } diff --git a/TournamentAPI.UnitTests/packages.lock.json b/TournamentAPI.UnitTests/packages.lock.json index dcb41bd..82ecdb3 100644 --- a/TournamentAPI.UnitTests/packages.lock.json +++ b/TournamentAPI.UnitTests/packages.lock.json @@ -80,6 +80,11 @@ "Yarp.ReverseProxy": "2.3.0" } }, + "Google.Protobuf": { + "type": "Transitive", + "resolved": "3.30.1", + "contentHash": "HeWXDQBabQn/sCGicbeLJ0HMunknfC4FdLrOQOsaMJHcpqx3HVIpyyJqTrqJlWnza870twhOb+rBcaTiC/TlNA==" + }, "GreenDonut": { "type": "Transitive", "resolved": "15.1.15", @@ -127,6 +132,28 @@ "resolved": "15.1.15", "contentHash": "zKYnY8NMsZvQSY3Mdwtkivu4K/uMCSSjAB9YDiXV54mDwehnYVlFzMTX9MzB34+f8menzcZ8Ko/tqzlhKSt8Sw==" }, + "Grpc.Core.Api": { + "type": "Transitive", + "resolved": "2.70.0", + "contentHash": "66UotvWcSIq41oiQhLWcQACyKPM4umxXNiht5DQTLZJfNwEswWOcS7Z0xIEHyNIBE7ZpjotH22bEjTkvhPxmVw==" + }, + "Grpc.Net.Client": { + "type": "Transitive", + "resolved": "2.70.0", + "contentHash": "xNv0FFCVJa5S1beUtye82WFCxKThuE1jbN8DO1x1Rj8VSIWXLBUmfSID5a1fGzsU2R/EMfwPoWclJ2RMfQuGXw==", + "dependencies": { + "Grpc.Net.Common": "2.70.0", + "Microsoft.Extensions.Logging.Abstractions": "6.0.0" + } + }, + "Grpc.Net.Common": { + "type": "Transitive", + "resolved": "2.70.0", + "contentHash": "rBdEUMyCwa+iB8mqC6JKyPbj3SBHHkReJj/yy/XKJI63GcG6w9DJMMGTVcYHqq4Ci2W4m0HT4jt2pFfFscar8g==", + "dependencies": { + "Grpc.Core.Api": "2.70.0" + } + }, "HotChocolate": { "type": "Transitive", "resolved": "15.1.15", @@ -664,6 +691,11 @@ "resolved": "9.0.11", "contentHash": "+ZxxZzcVU+IEzq12GItUzf/V3mEc5nSLiXijwvDc4zyhbjvSZZ043giSZqGnhakrjwRWjkerIHPrRwm9okEIpw==" }, + "Microsoft.Extensions.DependencyModel": { + "type": "Transitive", + "resolved": "9.0.0", + "contentHash": "saxr2XzwgDU77LaQfYFXmddEDRUKHF4DaGMZkNB3qjdVSZlax3//dGJagJkKrGMIPNZs2jVFXITyCCR6UHJNdA==" + }, "Microsoft.Extensions.Diagnostics.Abstractions": { "type": "Transitive", "resolved": "9.0.11", @@ -943,6 +975,52 @@ "OpenTelemetry.Api": "1.15.3" } }, + "Serilog.Extensions.Hosting": { + "type": "Transitive", + "resolved": "9.0.0", + "contentHash": "u2TRxuxbjvTAldQn7uaAwePkWxTHIqlgjelekBtilAGL5sYyF3+65NWctN4UrwwGLsDC7c3Vz3HnOlu+PcoxXg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0", + "Microsoft.Extensions.Hosting.Abstractions": "9.0.0", + "Microsoft.Extensions.Logging.Abstractions": "9.0.0", + "Serilog": "4.2.0", + "Serilog.Extensions.Logging": "9.0.0" + } + }, + "Serilog.Formatting.Compact": { + "type": "Transitive", + "resolved": "3.0.0", + "contentHash": "wQsv14w9cqlfB5FX2MZpNsTawckN4a8dryuNGbebB/3Nh1pXnROHZov3swtu3Nj5oNG7Ba+xdu7Et/ulAUPanQ==", + "dependencies": { + "Serilog": "4.0.0" + } + }, + "Serilog.Settings.Configuration": { + "type": "Transitive", + "resolved": "9.0.0", + "contentHash": "4/Et4Cqwa+F88l5SeFeNZ4c4Z6dEAIKbu3MaQb2Zz9F/g27T5a3wvfMcmCOaAiACjfUb4A6wrlTVfyYUZk3RRQ==", + "dependencies": { + "Microsoft.Extensions.Configuration.Binder": "9.0.0", + "Microsoft.Extensions.DependencyModel": "9.0.0", + "Serilog": "4.2.0" + } + }, + "Serilog.Sinks.Debug": { + "type": "Transitive", + "resolved": "3.0.0", + "contentHash": "4BzXcdrgRX7wde9PmHuYd9U6YqycCC28hhpKonK7hx0wb19eiuRj16fPcPSVp0o/Y1ipJuNLYQ00R3q2Zs8FDA==", + "dependencies": { + "Serilog": "4.0.0" + } + }, + "Serilog.Sinks.File": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "lxjg89Y8gJMmFxVkbZ+qDgjl+T4yC5F7WSLTvA+5q0R04tfKVLRL/EHpYoJ/MEQd2EeCKDuylBIVnAYMotmh2A==", + "dependencies": { + "Serilog": "4.0.0" + } + }, "System.Buffers": { "type": "Transitive", "resolved": "4.6.0", @@ -1148,7 +1226,9 @@ "OpenTelemetry.Instrumentation.AspNetCore": "[1.15.2, )", "OpenTelemetry.Instrumentation.Http": "[1.15.1, )", "Serilog": "[4.3.0, )", - "Serilog.Sinks.Console": "[6.1.1, )" + "Serilog.AspNetCore": "[9.0.0, )", + "Serilog.Sinks.Console": "[6.1.1, )", + "Serilog.Sinks.OpenTelemetry": "[4.2.0, )" } }, "AspNetCore.HealthChecks.SqlServer": { @@ -1341,6 +1421,31 @@ "resolved": "4.3.0", "contentHash": "+cDryFR0GRhsGOnZSKwaDzRRl4MupvJ42FhCE4zhQRVanX0Jpg6WuCBk59OVhVDPmab1bB+nRykAnykYELA9qQ==" }, + "Serilog.AspNetCore": { + "type": "CentralTransitive", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "JslDajPlBsn3Pww1554flJFTqROvK9zz9jONNQgn0D8Lx2Trw8L0A8/n6zEQK1DAZWXrJwiVLw8cnTR3YFuYsg==", + "dependencies": { + "Serilog": "4.2.0", + "Serilog.Extensions.Hosting": "9.0.0", + "Serilog.Formatting.Compact": "3.0.0", + "Serilog.Settings.Configuration": "9.0.0", + "Serilog.Sinks.Console": "6.0.0", + "Serilog.Sinks.Debug": "3.0.0", + "Serilog.Sinks.File": "6.0.0" + } + }, + "Serilog.Extensions.Logging": { + "type": "CentralTransitive", + "requested": "[9.0.2, )", + "resolved": "9.0.0", + "contentHash": "NwSSYqPJeKNzl5AuXVHpGbr6PkZJFlNa14CdIebVjK3k/76kYj/mz5kiTRNVSsSaxM8kAIa1kpy/qyT9E4npRQ==", + "dependencies": { + "Microsoft.Extensions.Logging": "9.0.0", + "Serilog": "4.2.0" + } + }, "Serilog.Sinks.Console": { "type": "CentralTransitive", "requested": "[6.1.1, )", @@ -1349,6 +1454,17 @@ "dependencies": { "Serilog": "4.0.0" } + }, + "Serilog.Sinks.OpenTelemetry": { + "type": "CentralTransitive", + "requested": "[4.2.0, )", + "resolved": "4.2.0", + "contentHash": "PzMCyE5G19tjr5IZEi5qg+4UU5QrxBEoBEMu/hhYybTrGKXqUDiSGWKZNUDBgelaVKqLADlsmlJVyKce5SyPrg==", + "dependencies": { + "Google.Protobuf": "3.30.1", + "Grpc.Net.Client": "2.70.0", + "Serilog": "4.2.0" + } } } } diff --git a/TournamentAPI/TournamentAPI.csproj b/TournamentAPI/TournamentAPI.csproj index fff4800..493f0d0 100644 --- a/TournamentAPI/TournamentAPI.csproj +++ b/TournamentAPI/TournamentAPI.csproj @@ -29,7 +29,9 @@ + + diff --git a/TournamentAPI/packages.lock.json b/TournamentAPI/packages.lock.json index fa57a95..4b12b9e 100644 --- a/TournamentAPI/packages.lock.json +++ b/TournamentAPI/packages.lock.json @@ -213,6 +213,21 @@ "resolved": "4.3.0", "contentHash": "+cDryFR0GRhsGOnZSKwaDzRRl4MupvJ42FhCE4zhQRVanX0Jpg6WuCBk59OVhVDPmab1bB+nRykAnykYELA9qQ==" }, + "Serilog.AspNetCore": { + "type": "Direct", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "JslDajPlBsn3Pww1554flJFTqROvK9zz9jONNQgn0D8Lx2Trw8L0A8/n6zEQK1DAZWXrJwiVLw8cnTR3YFuYsg==", + "dependencies": { + "Serilog": "4.2.0", + "Serilog.Extensions.Hosting": "9.0.0", + "Serilog.Formatting.Compact": "3.0.0", + "Serilog.Settings.Configuration": "9.0.0", + "Serilog.Sinks.Console": "6.0.0", + "Serilog.Sinks.Debug": "3.0.0", + "Serilog.Sinks.File": "6.0.0" + } + }, "Serilog.Sinks.Console": { "type": "Direct", "requested": "[6.1.1, )", @@ -222,6 +237,17 @@ "Serilog": "4.0.0" } }, + "Serilog.Sinks.OpenTelemetry": { + "type": "Direct", + "requested": "[4.2.0, )", + "resolved": "4.2.0", + "contentHash": "PzMCyE5G19tjr5IZEi5qg+4UU5QrxBEoBEMu/hhYybTrGKXqUDiSGWKZNUDBgelaVKqLADlsmlJVyKce5SyPrg==", + "dependencies": { + "Google.Protobuf": "3.30.1", + "Grpc.Net.Client": "2.70.0", + "Serilog": "4.2.0" + } + }, "AspNetCore.HealthChecks.UI.Core": { "type": "Transitive", "resolved": "9.0.0", @@ -267,6 +293,11 @@ "Yarp.ReverseProxy": "2.3.0" } }, + "Google.Protobuf": { + "type": "Transitive", + "resolved": "3.30.1", + "contentHash": "HeWXDQBabQn/sCGicbeLJ0HMunknfC4FdLrOQOsaMJHcpqx3HVIpyyJqTrqJlWnza870twhOb+rBcaTiC/TlNA==" + }, "GreenDonut": { "type": "Transitive", "resolved": "15.1.15", @@ -314,6 +345,28 @@ "resolved": "15.1.15", "contentHash": "zKYnY8NMsZvQSY3Mdwtkivu4K/uMCSSjAB9YDiXV54mDwehnYVlFzMTX9MzB34+f8menzcZ8Ko/tqzlhKSt8Sw==" }, + "Grpc.Core.Api": { + "type": "Transitive", + "resolved": "2.70.0", + "contentHash": "66UotvWcSIq41oiQhLWcQACyKPM4umxXNiht5DQTLZJfNwEswWOcS7Z0xIEHyNIBE7ZpjotH22bEjTkvhPxmVw==" + }, + "Grpc.Net.Client": { + "type": "Transitive", + "resolved": "2.70.0", + "contentHash": "xNv0FFCVJa5S1beUtye82WFCxKThuE1jbN8DO1x1Rj8VSIWXLBUmfSID5a1fGzsU2R/EMfwPoWclJ2RMfQuGXw==", + "dependencies": { + "Grpc.Net.Common": "2.70.0", + "Microsoft.Extensions.Logging.Abstractions": "6.0.0" + } + }, + "Grpc.Net.Common": { + "type": "Transitive", + "resolved": "2.70.0", + "contentHash": "rBdEUMyCwa+iB8mqC6JKyPbj3SBHHkReJj/yy/XKJI63GcG6w9DJMMGTVcYHqq4Ci2W4m0HT4jt2pFfFscar8g==", + "dependencies": { + "Grpc.Core.Api": "2.70.0" + } + }, "HotChocolate": { "type": "Transitive", "resolved": "15.1.15", @@ -1190,6 +1243,52 @@ "OpenTelemetry.Api": "1.15.3" } }, + "Serilog.Extensions.Hosting": { + "type": "Transitive", + "resolved": "9.0.0", + "contentHash": "u2TRxuxbjvTAldQn7uaAwePkWxTHIqlgjelekBtilAGL5sYyF3+65NWctN4UrwwGLsDC7c3Vz3HnOlu+PcoxXg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0", + "Microsoft.Extensions.Hosting.Abstractions": "9.0.0", + "Microsoft.Extensions.Logging.Abstractions": "9.0.0", + "Serilog": "4.2.0", + "Serilog.Extensions.Logging": "9.0.0" + } + }, + "Serilog.Formatting.Compact": { + "type": "Transitive", + "resolved": "3.0.0", + "contentHash": "wQsv14w9cqlfB5FX2MZpNsTawckN4a8dryuNGbebB/3Nh1pXnROHZov3swtu3Nj5oNG7Ba+xdu7Et/ulAUPanQ==", + "dependencies": { + "Serilog": "4.0.0" + } + }, + "Serilog.Settings.Configuration": { + "type": "Transitive", + "resolved": "9.0.0", + "contentHash": "4/Et4Cqwa+F88l5SeFeNZ4c4Z6dEAIKbu3MaQb2Zz9F/g27T5a3wvfMcmCOaAiACjfUb4A6wrlTVfyYUZk3RRQ==", + "dependencies": { + "Microsoft.Extensions.Configuration.Binder": "9.0.0", + "Microsoft.Extensions.DependencyModel": "9.0.0", + "Serilog": "4.2.0" + } + }, + "Serilog.Sinks.Debug": { + "type": "Transitive", + "resolved": "3.0.0", + "contentHash": "4BzXcdrgRX7wde9PmHuYd9U6YqycCC28hhpKonK7hx0wb19eiuRj16fPcPSVp0o/Y1ipJuNLYQ00R3q2Zs8FDA==", + "dependencies": { + "Serilog": "4.0.0" + } + }, + "Serilog.Sinks.File": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "lxjg89Y8gJMmFxVkbZ+qDgjl+T4yC5F7WSLTvA+5q0R04tfKVLRL/EHpYoJ/MEQd2EeCKDuylBIVnAYMotmh2A==", + "dependencies": { + "Serilog": "4.0.0" + } + }, "System.Buffers": { "type": "Transitive", "resolved": "4.6.0", @@ -1404,6 +1503,16 @@ "dependencies": { "System.IO.Hashing": "8.0.0" } + }, + "Serilog.Extensions.Logging": { + "type": "CentralTransitive", + "requested": "[9.0.2, )", + "resolved": "9.0.0", + "contentHash": "NwSSYqPJeKNzl5AuXVHpGbr6PkZJFlNa14CdIebVjK3k/76kYj/mz5kiTRNVSsSaxM8kAIa1kpy/qyT9E4npRQ==", + "dependencies": { + "Microsoft.Extensions.Logging": "9.0.0", + "Serilog": "4.2.0" + } } } } From 8647c95f4a8bc9f6039dacbc67caa309eaa80684 Mon Sep 17 00:00:00 2001 From: Dejmenek Date: Fri, 15 May 2026 17:37:21 +0200 Subject: [PATCH 4/5] build: add Loki service to docker-compose setup Added a Loki service using the grafana/loki:latest image to docker-compose.yml. The service exposes port 3100, mounts configuration and data volumes, and uses a custom config file. Also introduced a named Docker volume for Loki data persistence. --- docker-compose.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 45f5cc0..d09c9ac 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,6 +11,17 @@ services: - ./prometheus/:/etc/prometheus/ - prometheus:/prometheus + loki: + image: grafana/loki:latest + container_name: loki + command: + - '--config.file=/etc/loki/loki.yml' + ports: + - 3100:3100 + volumes: + - ./loki/:/etc/loki/ + - loki:/loki + grafana: image: grafana/grafana:latest container_name: grafana @@ -21,4 +32,5 @@ services: volumes: prometheus: - grafana: \ No newline at end of file + grafana: + loki: \ No newline at end of file From c86fb5190a5373ae64f153aa2ff8c9d840cb5dfa Mon Sep 17 00:00:00 2001 From: Dejmenek Date: Fri, 15 May 2026 17:38:13 +0200 Subject: [PATCH 5/5] feat: add OpenTelemetry logging with Serilog Switched to .CreateBootstrapLogger() for early logging during startup. Integrated Serilog.Sinks.OpenTelemetry to export logs to an OTLP endpoint (http://localhost:3100/otlp) using HTTP Protobuf protocol. Set the service name to "TournamentAPI" for log resource attributes. Added the required using statement for Serilog.Sinks.OpenTelemetry. --- TournamentAPI/Program.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/TournamentAPI/Program.cs b/TournamentAPI/Program.cs index df1fc92..9283731 100644 --- a/TournamentAPI/Program.cs +++ b/TournamentAPI/Program.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.AspNetCore.Identity; using Serilog; +using Serilog.Sinks.OpenTelemetry; using TournamentAPI.Configuration.Extensions; using TournamentAPI.Data; using TournamentAPI.Data.Models; @@ -11,7 +12,7 @@ Log.Logger = new LoggerConfiguration() .WriteTo.Console() - .CreateLogger(); + .CreateBootstrapLogger(); builder.Services.AddApplicationOptions(); @@ -29,6 +30,19 @@ builder.Services.AddScoped(); +builder.Services.AddSerilog((_, loggerConfiguration) => + loggerConfiguration + .WriteTo.Console() + .WriteTo.OpenTelemetry(opt => + { + opt.Endpoint = new Uri("http://localhost:3100/otlp").ToString(); + opt.Protocol = OtlpProtocol.HttpProtobuf; + opt.ResourceAttributes = new Dictionary + { + ["service.name"] = "TournamentAPI" + }; + })); + var app = builder.Build(); if (app.Environment.IsDevelopment())