Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add example for OTLP logging via stdout and k8s #547

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/renovate.json5
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,18 @@
"matchPackageNames": ["eclipse-temurin"],
"enabled": false
}
],
"customManagers": [
{
"customType": "regex",
"description": "Update _VERSION variables in Dockerfiles",
"fileMatch": [
"(^|/|\\.)Dockerfile$",
"(^|/)Dockerfile\\.[^/]*$"
],
"matchStrings": [
"# renovate: datasource=(?<datasource>[a-z-]+?)(?: depName=(?<depName>.+?))? packageName=(?<packageName>.+?)(?: versioning=(?<versioning>[a-z-]+?))?\\s(?:ENV|ARG) .+?_VERSION=(?<currentValue>.+?)\\s"
]
}
Comment on lines +38 to +49
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting 👍

]
}
15 changes: 15 additions & 0 deletions .github/scripts/run-acceptance-tests.sh
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe rename to run-oats-tests.sh? (and rename workflow to oats-tests.yml)

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env bash

set -euo pipefail

pushd logging-otlp
../gradlew assemble
popd

wget -q -O - https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash

cd oats/yaml
go install github.com/onsi/ginkgo/v2/ginkgo@latest
export TESTCASE_TIMEOUT=5m
export TESTCASE_BASE_PATH=../..
ginkgo -r
46 changes: 46 additions & 0 deletions .github/workflows/acceptance-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Acceptance Tests

on:
push:
branches:
- main
pull_request:
workflow_dispatch:

jobs:
acceptance-tests:
runs-on: ubuntu-24.04
steps:
- name: Check out
uses: actions/checkout@v4

- name: Set up JDK for running Gradle
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17

- name: Set up gradle
uses: gradle/actions/setup-gradle@v4
with:
cache-read-only: ${{ github.event_name == 'pull_request' }}

- name: Check out oats
uses: actions/checkout@v4
with:
repository: grafana/oats
ref: bc2f08c5e55114234cece216290f1580a75a6032
path: oats
- name: Set up Go
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(sorry, just a nitpicky preference I have)

Suggested change
- name: Set up Go
- name: Set up Go

uses: actions/setup-go@v5
with:
go-version: '1.23'
cache-dependency-path: oats/go.sum
- name: Run acceptance tests
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- name: Run acceptance tests
- name: Run acceptance tests

run: .github/scripts/run-acceptance-tests.sh
- name: upload log file
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- name: upload log file
- name: upload log file

uses: actions/upload-artifact@v4
if: failure()
with:
name: OATS logs
path: oats/yaml/build/**/*.log
13 changes: 13 additions & 0 deletions logging-k8s-stdout-otlp-json/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM eclipse-temurin:21-jre

WORKDIR /usr/src/app/

# renovate: datasource=github-releases depName=opentelemetry-java-instrumentation packageName=open-telemetry/opentelemetry-java-instrumentation
ENV OPENTELEMETRY_JAVA_INSTRUMENTATION_VERSION=v2.10.0

ADD build/libs/*SNAPSHOT.jar ./app.jar
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you think about adding the ../gradlew assemble here, and then I think could remove it from (the global) run-acceptance-tests.sh file?

ADD --chmod=644 https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/$OPENTELEMETRY_JAVA_INSTRUMENTATION_VERSION/opentelemetry-javaagent.jar ./opentelemetry-javaagent.jar
ENV JAVA_TOOL_OPTIONS=-javaagent:./opentelemetry-javaagent.jar

EXPOSE 8080
ENTRYPOINT [ "java", "-jar", "./app.jar" ]
37 changes: 37 additions & 0 deletions logging-k8s-stdout-otlp-json/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Exporting Application logs using JSON logging in Kubernetes

If you want to get logs from your Java application ingested into an
OpenTelemetry-compatible logs backend, the easiest and recommended way is using
an OpenTelemetry protocol (OTLP) exporter,
which is explained in the [logging](../logging) example.

However, some scenarios require logs
to be output to files or stdout due to organizational or reliability needs.
Refer to [Collecting OpenTelemetry-compliant Java logs from files](https://opentelemetry.io/blog/2024/collecting-otel-compliant-java-logs-from-files/) for more details.

This example contains

- a Java application that uses the experimental
[experimental-otlp/stdout](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/configuration/sdk-environment-variables.md#in-development-exporter-selection) logs exporter
- a OTel collector configuration that uses the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- a OTel collector configuration that uses the
- an OpenTelemetry collector configuration that uses the

[OTLP/JSON connector](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/connector/otlpjsonconnector) to turn Pod logs into `OTLP`


![OTLP/JSON Architecture](otlpjson-architecture.png)

## Getting Started

The k8s directory contains the Kubernetes manifests to deploy the application and the collector.

Ignore the `lgtm.yaml` file, which is only used for running locally and automated testing
using [OATs](https://github.com/grafana/oats).

## Running locally

You can run the application locally using the following steps:

1. Run [k3d.sh](./k3d.sh) to start a local Kubernetes cluster with all the necessary components.
2. Generate traffic using [generate-traffic.sh](./generate-traffic.sh)
3. Log in to [http://localhost:3000](http://localhost:3000)
4. Go to "Explore"
5. Select "Loki" as data source to view the logs
24 changes: 24 additions & 0 deletions logging-k8s-stdout-otlp-json/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import org.springframework.boot.gradle.plugin.SpringBootPlugin
import org.springframework.boot.gradle.tasks.bundling.BootJar

plugins {
id("java")
id("org.springframework.boot") version "3.4.0"
}

description = "OpenTelemetry Example for Java Agent with Stdout logging"
val moduleName by extra { "io.opentelemetry.examples.javagent.logging-k8s-stdout-otlp-json" }

java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}

dependencies {
implementation(platform(SpringBootPlugin.BOM_COORDINATES))

implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-actuator")
}

5 changes: 5 additions & 0 deletions logging-k8s-stdout-otlp-json/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash

set -euo pipefail

docker build -f Dockerfile -t "dice:1.1-SNAPSHOT" .
3 changes: 3 additions & 0 deletions logging-k8s-stdout-otlp-json/generate-traffic.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash

watch 'curl -s http://localhost:8080/rolldice'
15 changes: 15 additions & 0 deletions logging-k8s-stdout-otlp-json/k3d.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/bash

set -euo pipefail

./build.sh
k3d cluster create jsonlogging || k3d cluster start jsonlogging
k3d image import -c jsonlogging dice:1.1-SNAPSHOT
Comment on lines +5 to +7
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since this file already has a dependency on the dice image, does it make sense to just inline the one line here?


kubectl apply -f k8s/

kubectl wait --for=condition=ready pod -l app=dice
kubectl wait --for=condition=ready --timeout=5m pod -l app=lgtm

kubectl port-forward service/dice 8080:8080 &
kubectl port-forward service/lgtm 3000:3000 &
72 changes: 72 additions & 0 deletions logging-k8s-stdout-otlp-json/k8s/collector-configmap.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: otel-collector-config
data:
otel-collector-config.yaml: |-
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
prometheus/collector: # needed if you use the docker-lgtm image
zeitlinger marked this conversation as resolved.
Show resolved Hide resolved
config:
scrape_configs:
- job_name: 'opentelemetry-collector'
static_configs:
- targets: [ 'localhost:8888' ]
filelog/otlp-json-logs:
include:
- /var/log/pods/*/*/*.log
include_file_path: true
operators:
- id: container-parser
type: container

processors:
zeitlinger marked this conversation as resolved.
Show resolved Hide resolved
batch:
resourcedetection:
detectors: [ "env", "system" ]
override: false

zeitlinger marked this conversation as resolved.
Show resolved Hide resolved
connectors:
otlpjson:

exporters:
otlphttp/metrics:
endpoint: http://localhost:9090/api/v1/otlp
otlphttp/traces:
endpoint: http://localhost:4418
otlphttp/logs:
endpoint: http://localhost:3100/otlp
debug/metrics:
verbosity: detailed
debug/traces:
verbosity: detailed
debug/logs:
verbosity: detailed
nop:

service:
pipelines:
traces:
receivers: [ otlp ]
processors: [ batch ]
zeitlinger marked this conversation as resolved.
Show resolved Hide resolved
exporters: [ otlphttp/traces ]
metrics:
receivers: [ otlp, prometheus/collector ]
zeitlinger marked this conversation as resolved.
Show resolved Hide resolved
processors: [ batch ]
zeitlinger marked this conversation as resolved.
Show resolved Hide resolved
exporters: [ otlphttp/metrics ]
logs/raw_otlpjson:
receivers: [ filelog/otlp-json-logs ]
# (i) no need for processors before the otlpjson connector
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does (i) mean?

# Declare processors in the shared "logs" pipeline below
processors: [ ]
exporters: [ otlpjson ]
logs/otlp:
receivers: [ otlp, otlpjson ]
processors: [ resourcedetection, batch ]
zeitlinger marked this conversation as resolved.
Show resolved Hide resolved
exporters: [ otlphttp/logs ]
# exporters: [ otlphttp/logs, debug/logs ] # Uncomment this line to enable debug logging
48 changes: 48 additions & 0 deletions logging-k8s-stdout-otlp-json/k8s/dice.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
apiVersion: v1
kind: Service
metadata:
name: dice
spec:
selector:
app: dice
ports:
- protocol: TCP
port: 8080
targetPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: dice
spec:
replicas: 1
selector:
matchLabels:
app: dice
template:
metadata:
labels:
app: dice
spec:
containers:
- name: dice
image: dice:1.1-SNAPSHOT
imagePullPolicy: Never
ports:
- containerPort: 8080
env:
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "http://lgtm:4318"
- name: OTEL_LOGS_EXPORTER
value: "experimental-otlp/stdout"
- name: OTEL_RESOURCE_ATTRIBUTES
value: service.name=dice,service.namespace=shop,service.version=1.1,deployment.environment=staging
- name: OTEL_INSTRUMENTATION_LOGBACK_APPENDER_EXPERIMENTAL_LOG_ATTRIBUTES
value: "true"
- name: OTEL_INSTRUMENTATION_LOGBACK_APPENDER_EXPERIMENTAL_CAPTURE_KEY_VALUE_PAIR_ATTRIBUTES
value: "true"
- name: OTEL_INSTRUMENTATION_LOGBACK_APPENDER_EXPERIMENTAL_CAPTURE_MDC_ATTRIBUTES
value: "true"
- name: SERVICE_NAME
value: dice

86 changes: 86 additions & 0 deletions logging-k8s-stdout-otlp-json/k8s/lgtm.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
apiVersion: v1
kind: Service
metadata:
name: lgtm
spec:
selector:
app: lgtm
ports:
- name: grafana
protocol: TCP
port: 3000
targetPort: 3000
- name: otel-grpc
protocol: TCP
port: 4317
targetPort: 4317
- name: otel-http
protocol: TCP
port: 4318
targetPort: 4318
- name: prometheus # needed for automated tests
protocol: TCP
port: 9090
targetPort: 9090
- name: loki # needed for automated tests
protocol: TCP
port: 3100
targetPort: 3100
- name: tempo # needed for automated tests
protocol: TCP
port: 3200
targetPort: 3200
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: lgtm
spec:
replicas: 1
selector:
matchLabels:
app: lgtm
template:
metadata:
labels:
app: lgtm
spec:
containers:
- name: lgtm
image: grafana/otel-lgtm:latest
ports:
- containerPort: 3000
- containerPort: 4317
- containerPort: 4318
- containerPort: 9090 # needed for automated tests
- containerPort: 3100 # needed for automated tests
- containerPort: 3200 # needed for automated tests
readinessProbe:
exec:
command:
- cat
- /tmp/ready
volumeMounts:
- mountPath: /otel-lgtm/otelcol-config.yaml
name: otel-collector-config
subPath: otel-collector-config.yaml
readOnly: true
- mountPath: /var/log
name: varlog
readOnly: true
- mountPath: /var/lib/docker/containers
name: varlibdockercontainers
readOnly: true
env:
- name: ENABLE_LOGS_OTELCOL
value: "true"
volumes:
- name: otel-collector-config
configMap:
name: otel-collector-config
- name: varlog
hostPath:
path: /var/log
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers
Loading
Loading