Intro
This application outputs logs to the OpenTelemetry service, which collects them. Then, it pushes the logs to the Grafana Loki service for aggregation and storage. Finally, Grafana is used to visualize and analyze the log data from Grafana Loki.
Configuration
Using the environment variables from docker-compose-config.txt
, they are passed into docker-compose.yaml
. Additionally, loki-config.yaml
is used to configure Grafana Loki, and otel-collector-config.yaml
is used to configure the OpenTelemetry collector. Logs are stored in MiniO, while Postgres is used to store Grafana's configuration.
- docker-compose.yaml
services: # I use .NET 8 to develop the application dotnet-app: build: context: . dockerfile: Dockerfile # Remeber to add Dockerfile of the application container_name: dotnet-app environment: ASPNETCORE_URLS: http://+:5000 DOTNET_LOG_LEVEL: Information ports: - "${DOTNET_APP_PORT}:5000" # .NET App port depends_on: - opentelemetry-collector-svc # Loki (Log storage) loki-svc: image: grafana/loki:3.3.2 container_name: loki-svc ports: - "3100" # Loki API port command: -config.expand-env=true -config.file=/etc/loki/local-config.yaml environment: MINIO_USER: ${MINIO_USER} MINIO_PASSWORD: ${MINIO_PASSWORD} MINIO_PORT: ${MINIO_PORT} volumes: - ./loki-config.yaml:/etc/loki/local-config.yaml depends_on: - minio-svc postgres-grafana-svc: image: postgres container_name: postgres-grafana-svc # set shared memory limit when using docker-compose shm_size: 128mb environment: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_GRAFANA_DB} ports: - "${POSTGRES_GRAFANA_PORT}:5432" volumes: - postgres_grafana_data:/var/lib/postgresql/data # Grafana (Visualization) grafana-svc: image: grafana/grafana container_name: grafana-svc ports: - "${GRAFANA_PORT}:3000" # Grafana UI port environment: GF_DATABASE_TYPE: postgres GF_DATABASE_HOST: postgres-grafana-svc:5432 GF_DATABASE_NAME: ${POSTGRES_GRAFANA_DB} GF_DATABASE_USER: ${GF_SECURITY_ADMIN_USER} GF_DATABASE_PASSWORD: ${GF_SECURITY_ADMIN_PASSWORD} depends_on: - postgres-grafana-svc - loki-svc entrypoint: - sh - -euc - | mkdir -p /etc/grafana/provisioning/datasources cat <<EOF > /etc/grafana/provisioning/datasources/ds.yaml apiVersion: 1 datasources: - name: Loki type: loki access: proxy orgId: 1 url: http://loki-svc:3100 basicAuth: false isDefault: true version: 1 editable: false EOF /run.sh # OpenTelemetry Collector opentelemetry-collector-svc: image: ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector-contrib:latest container_name: opentelemetry-collector-svc ports: - "4318" # OTLP HTTP port volumes: - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml environment: OTEL_LOG_LEVEL: debug command: - "--config=/etc/otel-collector-config.yaml" depends_on: - loki-svc # MiniO (Object Storage) minio-svc: image: minio/minio container_name: minio-svc ports: - "${MINIO_PORT}:9000" # MiniO API port - "${MINIO_CONSOLE_PORT}:9001" # MiniO Console port for web ui environment: MINIO_ROOT_USER: ${MINIO_USER} MINIO_ROOT_PASSWORD: ${MINIO_PASSWORD} command: server /data --console-address ":9001" # 9001 container port volumes: - minio_data:/data volumes: minio_data: postgres_grafana_data:
- docker-compose-config.txt
POSTGRES_USER=admin POSTGRES_PASSWORD=admin POSTGRES_DB=app POSTGRES_PORT=15432 POSTGRES_GRAFANA_DB=grafana POSTGRES_GRAFANA_PORT=15433 DOTNET_APP_PORT=15000 GRAFANA_PORT=15300 GF_SECURITY_ADMIN_USER=admin GF_SECURITY_ADMIN_PASSWORD=admin MINIO_PORT=15900 MINIO_CONSOLE_PORT=15901 MINIO_USER=minioadmin MINIO_PASSWORD=minioadmin
- otel-collector-config.yaml
receivers: otlp: protocols: http: endpoint: 0.0.0.0:4318 processors: batch: exporters: otlphttp/loki: endpoint: http://loki-svc:3100/otlp tls: insecure: true debug: service: pipelines: logs: receivers: [otlp] processors: [batch] exporters: [otlphttp/loki, debug]
- loki-config.yaml
auth_enabled: false server: http_listen_port: 3100 http_server_write_timeout: 310s http_server_read_timeout: 310s common: path_prefix: /loki storage: s3: endpoint: minio-svc:9000 bucketnames: loki-bucket # important !! insecure: true access_key_id: ${MINIO_USER} secret_access_key: ${MINIO_PASSWORD} s3forcepathstyle: true # important !! replication_factor: 1 # important !! ring: kvstore: store: inmemory ingester: chunk_encoding: snappy chunk_idle_period: 2h chunk_target_size: 1536000 max_chunk_age: 2h querier: max_concurrent: 8 schema_config: configs: - from: "2024-04-01" store: tsdb object_store: s3 schema: v13 index: prefix: index_ period: 24h compactor: working_directory: /loki/compactor limits_config: max_query_parallelism: 24 split_queries_by_interval: 15m ingestion_rate_mb: 20 ingestion_burst_size_mb: 30 per_stream_rate_limit: "3MB" per_stream_rate_limit_burst: "10MB" query_timeout: 300s allow_structured_metadata: true ruler: storage: type: s3 s3: endpoint: minio-svc:9000 bucketnames: loki-rules # important !! insecure: true access_key_id: ${MINIO_USER} secret_access_key: ${MINIO_PASSWORD} s3forcepathstyle: true # important !!
In the Application (I used .NET 8)
- Install packages
OpenTelemetry OpenTelemetry.Exporter.OpenTelemetryProtocol OpenTelemetry.Extensions.Hosting
- Program.cs
builder.Logging.ClearProviders().AddConsole().AddOpenTelemetry(options => { options.IncludeScopes = true; options.IncludeFormattedMessage = true; options.SetResourceBuilder(ResourceBuilder.CreateDefault() .AddService(serviceName: "dotnet-app") // your service name .AddAttributes(new Dictionary<string, object> { // add whatever you want ["environment.name"] = "dev", })); options.AddOtlpExporter(otlpOptions => { otlpOptions.Protocol = OpenTelemetry.Exporter.OtlpExportProtocol.HttpProtobuf; otlpOptions.Endpoint = new Uri("http://opentelemetry-collector-svc:4318/v1/logs"); // This URL is only for logs, not trace or metric }); });
Test
1.start all services, and add two buckets loki-bucket
and loki-rules
in MiniO
docker-compose --env-file docker-compose-config.txt up --build -d
2.create logs in app, then go to http://localhost:15300/connections/datasources
4.input service_name
= "dotnet-app", Line Contains
= "I am Log" (your logs), and click "Run query" (blue button)
- clean all services
docker-compose down -v
Note
In Loki service, it is ok to see like this.
caller=ratestore.go:109 msg="error getting ingester clients" err="empty ring"
Top comments (0)