diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 21b86bf012..4db7acaf1d 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -35,5 +35,18 @@ "matchPackageNames": ["eclipse-temurin"], "enabled": false } + ], + "customManagers": [ + { + "customType": "regex", + "description": "Update _VERSION variables in Dockerfiles", + "fileMatch": [ + "(^|/|\\.)Dockerfile$", + "(^|/)Dockerfile\\.[^/]*$" + ], + "matchStrings": [ + "# renovate: datasource=(?[a-z-]+?)(?: depName=(?.+?))? packageName=(?.+?)(?: versioning=(?[a-z-]+?))?\\s(?:ENV|ARG) .+?_VERSION=(?.+?)\\s" + ] + } ] } diff --git a/.github/scripts/run-oats-tests.sh b/.github/scripts/run-oats-tests.sh new file mode 100755 index 0000000000..2dcfd34c13 --- /dev/null +++ b/.github/scripts/run-oats-tests.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +set -euo pipefail + +pushd logging-k8s-stdout-otlp-json +../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 diff --git a/.github/workflows/oats-tests.yml b/.github/workflows/oats-tests.yml new file mode 100644 index 0000000000..e969e44a56 --- /dev/null +++ b/.github/workflows/oats-tests.yml @@ -0,0 +1,51 @@ +name: OATS Tests + +on: + pull_request: + branches: + - main + paths: + - .github/workflows/oats-tests.yml + - 'logging-k8s-stdout-otlp-json/**' + 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: v0.1.0 + path: oats + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.23' + cache-dependency-path: oats/go.sum + + - name: Run acceptance tests + run: .github/scripts/run-oats-tests.sh + + - name: upload log file + uses: actions/upload-artifact@v4 + if: failure() + with: + name: OATS logs + path: oats/yaml/build/**/*.log diff --git a/logging-k8s-stdout-otlp-json/Dockerfile b/logging-k8s-stdout-otlp-json/Dockerfile new file mode 100644 index 0000000000..522405dd7b --- /dev/null +++ b/logging-k8s-stdout-otlp-json/Dockerfile @@ -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 +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" ] diff --git a/logging-k8s-stdout-otlp-json/README.md b/logging-k8s-stdout-otlp-json/README.md new file mode 100644 index 0000000000..788b362892 --- /dev/null +++ b/logging-k8s-stdout-otlp-json/README.md @@ -0,0 +1,43 @@ +# 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 +- 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` + +## Architecture + +![OTLP/JSON Architecture](otlpjson-architecture.png) + +The OpenTelemetry Collector pipeline: + +![OpenTelemetry Collector Pipeline](otel-collector-otlpjson-pipeline.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 with +[LGTM](https://github.com/grafana/docker-otel-lgtm/) +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 diff --git a/logging-k8s-stdout-otlp-json/build.gradle.kts b/logging-k8s-stdout-otlp-json/build.gradle.kts new file mode 100644 index 0000000000..bf8ba3aa7a --- /dev/null +++ b/logging-k8s-stdout-otlp-json/build.gradle.kts @@ -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") +} + diff --git a/logging-k8s-stdout-otlp-json/generate-traffic.sh b/logging-k8s-stdout-otlp-json/generate-traffic.sh new file mode 100755 index 0000000000..469ef6f85d --- /dev/null +++ b/logging-k8s-stdout-otlp-json/generate-traffic.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +watch 'curl -s http://localhost:8080/rolldice' diff --git a/logging-k8s-stdout-otlp-json/k3d.sh b/logging-k8s-stdout-otlp-json/k3d.sh new file mode 100755 index 0000000000..74552dc44e --- /dev/null +++ b/logging-k8s-stdout-otlp-json/k3d.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +set -euo pipefail + +docker build -f Dockerfile -t "dice:1.1-SNAPSHOT" . +k3d cluster create jsonlogging || k3d cluster start jsonlogging +k3d image import -c jsonlogging dice:1.1-SNAPSHOT + +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 & diff --git a/logging-k8s-stdout-otlp-json/k8s/collector-configmap.yaml b/logging-k8s-stdout-otlp-json/k8s/collector-configmap.yaml new file mode 100644 index 0000000000..5a53fea874 --- /dev/null +++ b/logging-k8s-stdout-otlp-json/k8s/collector-configmap.yaml @@ -0,0 +1,75 @@ +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 + 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: + batch: + resourcedetection: + detectors: [ "env", "system" ] + override: false + + k8sattributes: + # Config details in + # https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/k8sattributesprocessor + 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: [ k8sattributes, resourcedetection, batch ] + exporters: [ otlphttp/traces ] + metrics: + receivers: [ otlp, prometheus/collector ] + processors: [ k8sattributes, resourcedetection, batch ] + exporters: [ otlphttp/metrics ] + logs/raw_otlpjson: + receivers: [ filelog/otlp-json-logs ] + # No need for processors before the otlpjson connector + # Declare processors in the shared "logs" pipeline below + processors: [ ] + exporters: [ otlpjson ] + logs/otlp: + receivers: [ otlp, otlpjson ] + processors: [ k8sattributes, resourcedetection, batch ] + exporters: [ otlphttp/logs ] + # exporters: [ otlphttp/logs, debug/logs ] # Uncomment this line to enable debug logging diff --git a/logging-k8s-stdout-otlp-json/k8s/dice.yaml b/logging-k8s-stdout-otlp-json/k8s/dice.yaml new file mode 100644 index 0000000000..97abb7666b --- /dev/null +++ b/logging-k8s-stdout-otlp-json/k8s/dice.yaml @@ -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 + diff --git a/logging-k8s-stdout-otlp-json/k8s/lgtm.yaml b/logging-k8s-stdout-otlp-json/k8s/lgtm.yaml new file mode 100644 index 0000000000..7d8f6cc2cf --- /dev/null +++ b/logging-k8s-stdout-otlp-json/k8s/lgtm.yaml @@ -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 diff --git a/logging-k8s-stdout-otlp-json/oats.yaml b/logging-k8s-stdout-otlp-json/oats.yaml new file mode 100644 index 0000000000..cd009c18de --- /dev/null +++ b/logging-k8s-stdout-otlp-json/oats.yaml @@ -0,0 +1,54 @@ +# OATS is an acceptance testing framework for OpenTelemetry - https://github.com/grafana/oats + +kubernetes: + dir: k8s + app-service: dice + app-docker-file: Dockerfile + app-docker-tag: dice:1.1-SNAPSHOT + app-docker-port: 8080 +input: + - path: /rolldice +expected: + logs: + - logql: '{service_name="dice"} |~ `.*Anonymous player is rolling the dice:.*`' + regexp: 'Anonymous player is rolling the dice: \d+' # uses formatted message + - logql: '{service_name="dice"} |~ `.*simulating an error.*`' + equals: 'Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.RuntimeException: simulating an error] with root cause' + attributes: + deployment_environment: staging + exception_message: "simulating an error" + exception_type: "java.lang.RuntimeException" + scope_name: "org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/].[dispatcherServlet]" + service_name: dice + service_namespace: shop + service_version: 1.1 + severity_number: 17 + severity_text: SEVERE + exception_stacktrace: "java.lang.RuntimeException: simulating an error\n\tat com.grafana.example.RollController.index(RollController.java:21)\n\tat java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)\n\tat java.base/java.lang.reflect.Method.invoke(Unknown Source)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:255)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:188)\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891)\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1088)\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:978)\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)\n\tat org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903)\n\tat jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564)\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)\n\tat jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)\n\tat org.springframework.web.servlet.v6_0.OpenTelemetryHandlerMappingFilter.doFilter(OpenTelemetryHandlerMappingFilter.java:78)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)\n\tat org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:114)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167)\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483)\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115)\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)\n\tat org.apache.catalina.valves.RemoteIpValve.invoke(RemoteIpValve.java:731)\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344)\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:397)\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:905)\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741)\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1190)\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)\n\tat java.base/java.lang.Thread.run(Unknown Source)\n" + attribute-regexp: + flags: ".*" # from loki + detected_level: ".*" # from loki + observed_timestamp: ".*" # from loki + # thread_name: ".*" # thread name is missing when there is an exception - has nothing to do with stdout logging + span_id: ".*" + trace_id: ".*" + container_id: ".*" + host_arch: ".*" + host_name: ".*" + os_description: ".*" + os_type: ".*" + process_command_args: ".*" + process_executable_path: ".*" + process_pid: ".*" + process_runtime_description: ".*" + process_runtime_name: ".*" + process_runtime_version: ".*" + service_instance_id: ".*" + telemetry_distro_name: ".*" + telemetry_distro_version: ".*" + telemetry_sdk_language: ".*" + telemetry_sdk_name: ".*" + telemetry_sdk_version: ".*" + + no-extra-attributes: true + diff --git a/logging-k8s-stdout-otlp-json/otel-collector-otlpjson-pipeline.png b/logging-k8s-stdout-otlp-json/otel-collector-otlpjson-pipeline.png new file mode 100644 index 0000000000..67daac7e2a Binary files /dev/null and b/logging-k8s-stdout-otlp-json/otel-collector-otlpjson-pipeline.png differ diff --git a/logging-k8s-stdout-otlp-json/otlpjson-architecture.png b/logging-k8s-stdout-otlp-json/otlpjson-architecture.png new file mode 100644 index 0000000000..53d817a054 Binary files /dev/null and b/logging-k8s-stdout-otlp-json/otlpjson-architecture.png differ diff --git a/logging-k8s-stdout-otlp-json/src/main/java/com/grafana/example/RollController.java b/logging-k8s-stdout-otlp-json/src/main/java/com/grafana/example/RollController.java new file mode 100644 index 0000000000..99f4ac3a1e --- /dev/null +++ b/logging-k8s-stdout-otlp-json/src/main/java/com/grafana/example/RollController.java @@ -0,0 +1,35 @@ +package com.grafana.example; + +import java.util.Optional; +import java.util.Random; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class RollController { + + private static final Logger logger = LoggerFactory.getLogger(RollController.class); + private final Random random = new Random(0); + + @GetMapping("/rolldice") + public String index(@RequestParam("player") Optional player) throws InterruptedException { + Thread.sleep((long) (Math.abs((random.nextGaussian() + 1.0) * 200.0))); + if (random.nextInt(10) < 3) { + throw new RuntimeException("simulating an error"); + } + int result = this.getRandomNumber(1, 6); + if (player.isPresent()) { + logger.info("{} is rolling the dice: {}", player.get(), result); + } else { + logger.info("Anonymous player is rolling the dice: {}", result); + } + return Integer.toString(result); + } + + public int getRandomNumber(int min, int max) { + return random.nextInt(min, max + 1); + } +} diff --git a/logging-k8s-stdout-otlp-json/src/main/java/com/grafana/example/SpringBootApp.java b/logging-k8s-stdout-otlp-json/src/main/java/com/grafana/example/SpringBootApp.java new file mode 100644 index 0000000000..f375c0126a --- /dev/null +++ b/logging-k8s-stdout-otlp-json/src/main/java/com/grafana/example/SpringBootApp.java @@ -0,0 +1,14 @@ +package com.grafana.example; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.web.bind.annotation.RestController; + +@SpringBootApplication +@RestController +public class SpringBootApp { + + public static void main(String[] args) { + SpringApplication.run(SpringBootApp.class, args); + } +} diff --git a/logging-k8s-stdout-otlp-json/src/main/resources/logback-spring.xml b/logging-k8s-stdout-otlp-json/src/main/resources/logback-spring.xml new file mode 100644 index 0000000000..da72c91562 --- /dev/null +++ b/logging-k8s-stdout-otlp-json/src/main/resources/logback-spring.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/settings.gradle.kts b/settings.gradle.kts index f6d58b7068..143ce627a2 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -31,6 +31,7 @@ include( ":opentelemetry-examples-javaagent", ":opentelemetry-examples-log-appender", ":opentelemetry-examples-logging", + ":opentelemetry-examples-logging-k8s-stdout-otlp-json", ":opentelemetry-examples-manual-tracing", ":opentelemetry-examples-metrics", ":opentelemetry-examples-micrometer-shim",