Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
28 changes: 28 additions & 0 deletions observability-logs-dynatrace/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Git
.git
.gitignore

# Documentation
README.md
*.md

# Kubernetes manifests
k8s/
helm/

# IDE
.vscode
.idea
*.swp
*.swo
*~

# Test files
*_test.go

# Build artifacts
*.exe
*.exe~
*.dll
*.so
*.dylib
24 changes: 24 additions & 0 deletions observability-logs-dynatrace/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Copyright 2026 The OpenChoreo Authors
# SPDX-License-Identifier: Apache-2.0

FROM golang:1.27-alpine AS builder

WORKDIR /app
COPY go.mod go.sum* ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .

FROM alpine:3.24

RUN apk --no-cache add ca-certificates && \
addgroup -g 10500 appuser && \
adduser -D -u 10500 -G appuser appuser

WORKDIR /home/appuser
COPY --from=builder --chown=appuser:appuser --chmod=0550 /app/main .

USER appuser
EXPOSE 9098

CMD ["./main"]
18 changes: 18 additions & 0 deletions observability-logs-dynatrace/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
CFG_DIR := internal/api
OAPI_CODEGEN_VERSION ?= v2.5.1
SPEC := https://raw.githubusercontent.com/openchoreo/openchoreo/main/openapi/observability-logs-adapter-api.yaml

.PHONY: oapi-codegen-install openapi-codegen unit-test

oapi-codegen-install:
go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@$(OAPI_CODEGEN_VERSION)

openapi-codegen: oapi-codegen-install
cd $(CFG_DIR) && $(shell go env GOPATH)/bin/oapi-codegen --config cfg-models.yaml $(SPEC)
cd $(CFG_DIR) && $(shell go env GOPATH)/bin/oapi-codegen --config cfg-server.yaml $(SPEC)

MODULE_NAME := $(notdir $(CURDIR))

unit-test:
go test -coverprofile=coverage.out ./...
mv coverage.out ../$(MODULE_NAME)-coverage.out
312 changes: 312 additions & 0 deletions observability-logs-dynatrace/README.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions observability-logs-dynatrace/VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.1.0
22 changes: 22 additions & 0 deletions observability-logs-dynatrace/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module github.com/openchoreo/community-modules/observability-logs-dynatrace

go 1.26.0

require (
github.com/getkin/kin-openapi v0.149.0
github.com/google/uuid v1.6.0
github.com/oapi-codegen/runtime v1.7.0
golang.org/x/sync v0.23.0
)

require (
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/go-openapi/jsonpointer v0.22.5 // indirect
github.com/go-openapi/swag/jsonname v0.25.5 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/oasdiff/yaml v0.1.1 // indirect
github.com/oasdiff/yaml3 v0.0.14 // indirect
github.com/rogpeppe/go-internal v1.16.0 // indirect
github.com/santhosh-tekuri/jsonschema/v6 v6.0.3 // indirect
golang.org/x/text v0.32.0 // indirect
)
54 changes: 54 additions & 0 deletions observability-logs-dynatrace/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/getkin/kin-openapi v0.149.0 h1:ZbhmVJ4yq5RZDUsyP8lcBcGMsjsaTqXEFt6isdtMDfA=
github.com/getkin/kin-openapi v0.149.0/go.mod h1:1+BHDzstro+P5CKtPy1X4PfofnFgmRe6uvMy9+r9fKY=
github.com/go-openapi/jsonpointer v0.22.5 h1:8on/0Yp4uTb9f4XvTrM2+1CPrV05QPZXu+rvu2o9jcA=
github.com/go-openapi/jsonpointer v0.22.5/go.mod h1:gyUR3sCvGSWchA2sUBJGluYMbe1zazrYWIkWPjjMUY0=
github.com/go-openapi/swag/jsonname v0.25.5 h1:8p150i44rv/Drip4vWI3kGi9+4W9TdI3US3uUYSFhSo=
github.com/go-openapi/swag/jsonname v0.25.5/go.mod h1:jNqqikyiAK56uS7n8sLkdaNY/uq6+D2m2LANat09pKU=
github.com/go-openapi/testify/v2 v2.4.0 h1:8nsPrHVCWkQ4p8h1EsRVymA2XABB4OT40gcvAu+voFM=
github.com/go-openapi/testify/v2 v2.4.0/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/oapi-codegen/nullable v1.1.0 h1:eAh8JVc5430VtYVnq00Hrbpag9PFRGWLjxR1/3KntMs=
github.com/oapi-codegen/nullable v1.1.0/go.mod h1:KUZ3vUzkmEKY90ksAmit2+5juDIhIZhfDl+0PwOQlFY=
github.com/oapi-codegen/runtime v1.7.0 h1:t7358VYPvNbWJ9gdAkIK/smVeHpBf6yp8VTsaZsb/7k=
github.com/oapi-codegen/runtime v1.7.0/go.mod h1:GwV7hC2hviaMzj+ITfHVRESK5J2W/GefVwIND/bMGvU=
github.com/oasdiff/yaml v0.1.1 h1:6nHx+pn9gBRM6YpBlFZFQGCCd1nuvqOBtTD3KKTgGxY=
github.com/oasdiff/yaml v0.1.1/go.mod h1:EYJNoyktvWMJ0Hmhx+6qTaqMOsalUaRGT8Sj1hNcegU=
github.com/oasdiff/yaml3 v0.0.14 h1:aLJee3hxBK2H5wdXd9iPcIXb93Nty1Ge0pT171eHtkw=
github.com/oasdiff/yaml3 v0.0.14/go.mod h1:csto2xfDjYccdUn/yw/bPjj/cYTdp6HtFA0J4TWG+gg=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.16.0 h1:O9DK+vNMDVGLr2BeZqmpLeMjiMNkuXfcqntWbZV6S5g=
github.com/rogpeppe/go-internal v1.16.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.3 h1:1EYB5IzjZawrrnELUi78f9fPu57HuXjmddZPjrls/28=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.3/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk=
golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
23 changes: 23 additions & 0 deletions observability-logs-dynatrace/helm/.helmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
6 changes: 6 additions & 0 deletions observability-logs-dynatrace/helm/Chart.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
dependencies:
- name: fluent-bit
repository: https://fluent.github.io/helm-charts
version: 0.56.0
digest: sha256:e120960dbb5fbcfb6bccdd7bb486bd94fd013387bfd4490fada383d43f492cf5
generated: "2026-09-25T13:39:45.338966+05:30"
22 changes: 22 additions & 0 deletions observability-logs-dynatrace/helm/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copyright 2026 The OpenChoreo Authors
# SPDX-License-Identifier: Apache-2.0

apiVersion: v2
name: observability-logs-dynatrace
description: A Helm chart for OpenChoreo Logs Module for Dynatrace
type: application
version: 0.0.0-latest-dev
appVersion: "latest-dev"
keywords:
- dynatrace
- grail
- openchoreo
- logs
maintainers:
- name: OpenChoreo Team
home: https://github.com/openchoreo/community-modules
dependencies:
- name: fluent-bit
version: "0.56.0"
repository: https://fluent.github.io/helm-charts
condition: fluent-bit.enabled
83 changes: 83 additions & 0 deletions observability-logs-dynatrace/helm/templates/_helpers.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
{{/*
Copyright 2026 The OpenChoreo Authors
SPDX-License-Identifier: Apache-2.0
*/}}

{{/*
Log ingest base URL: dynatrace.ingestUrl, or dynatrace.platformUrl with ".apps." replaced
by ".live.", which is where SaaS serves the classic API.
*/}}
{{- define "logs-dynatrace.ingestUrl" -}}
{{- if .Values.dynatrace.ingestUrl -}}
{{- trimSuffix "/" .Values.dynatrace.ingestUrl -}}
{{- else if .Values.dynatrace.platformUrl -}}
{{- trimSuffix "/" (replace ".apps." ".live." .Values.dynatrace.platformUrl) -}}
{{- end -}}
{{- end -}}

{{/*
The ingest URL split into the pieces Fluent Bit's http output takes separately, as a
JSON dict: host, port, tls, basePath. Port defaults from the scheme.
*/}}
{{- define "logs-dynatrace.ingestEndpoint" -}}
{{- $raw := include "logs-dynatrace.ingestUrl" . -}}
{{- $u := urlParse $raw -}}
{{- if not (has $u.scheme (list "http" "https")) -}}
{{- fail (printf "dynatrace.ingestUrl %q must start with http:// or https://" $raw) -}}
{{- end -}}
{{- $tls := eq $u.scheme "https" -}}
{{- $hostPort := splitList ":" $u.host -}}
{{- $host := first $hostPort -}}
{{- $port := ternary "443" "80" $tls -}}
{{- if eq (len $hostPort) 2 -}}
{{- $port = last $hostPort -}}
{{- end -}}
{{- dict "host" $host "port" $port "tls" $tls "basePath" (trimSuffix "/" $u.path) | toJson -}}
{{- end -}}

{{/*
Validate required values and fail fast with a readable message.
*/}}
{{- define "logs-dynatrace.validate" -}}
{{- if .Values.adapter.enabled -}}
{{- if not .Values.dynatrace.platformUrl -}}
{{- fail "dynatrace.platformUrl is required when adapter.enabled=true. Example: --set dynatrace.platformUrl=https://abc12345.apps.dynatrace.com" -}}
{{- end -}}
{{- if not (has .Values.adapter.authMode (list "platformToken" "oauth")) -}}
{{- fail (printf "adapter.authMode must be platformToken or oauth, got %q" .Values.adapter.authMode) -}}
{{- end -}}
{{- end -}}
{{- if (index .Values "fluent-bit").enabled -}}
{{- if not (include "logs-dynatrace.ingestUrl" .) -}}
{{- fail "dynatrace.ingestUrl (or dynatrace.platformUrl) is required when fluent-bit.enabled=true" -}}
{{- end -}}
{{- if not .Values.fluentBitCustomizations.clusterInstance -}}
{{- fail "fluentBitCustomizations.clusterInstance is required. It names the cluster these records came from and cannot be defaulted." -}}
{{- end -}}
{{- if not (regexMatch "^[A-Za-z0-9._-]+$" .Values.fluentBitCustomizations.clusterInstance) -}}
{{- fail (printf "fluentBitCustomizations.clusterInstance %q may contain only letters, digits, '.', '_' and '-'" .Values.fluentBitCustomizations.clusterInstance) -}}
{{- end -}}
{{- if not (has .Values.dynatrace.ingest.authScheme (list "Api-Token" "Bearer")) -}}
{{- fail (printf "dynatrace.ingest.authScheme must be Api-Token or Bearer, got %q" .Values.dynatrace.ingest.authScheme) -}}
{{- end -}}
{{- end -}}
{{- range $k := list "containerLogsSource" "auditLogsSource" -}}
{{- if not (regexMatch "^[A-Za-z0-9._-]+$" (index $.Values.dynatrace $k)) -}}
{{- fail (printf "dynatrace.%s may contain only letters, digits, '.', '_' and '-'" $k) -}}
{{- end -}}
{{- end -}}
{{- if eq .Values.dynatrace.containerLogsSource .Values.dynatrace.auditLogsSource -}}
{{- fail "dynatrace.containerLogsSource and dynatrace.auditLogsSource must differ" -}}
{{- end -}}
{{- if .Values.dynatrace.credentials.create -}}
{{- if and (index .Values "fluent-bit").enabled (not .Values.dynatrace.credentials.ingestToken) -}}
{{- fail "dynatrace.credentials.ingestToken is required when dynatrace.credentials.create=true and fluent-bit.enabled=true" -}}
{{- end -}}
{{- if and .Values.adapter.enabled (eq .Values.adapter.authMode "platformToken") (not .Values.dynatrace.credentials.platformToken) -}}
{{- fail "dynatrace.credentials.platformToken is required when dynatrace.credentials.create=true and adapter.authMode=platformToken" -}}
{{- end -}}
{{- if and .Values.adapter.enabled (eq .Values.adapter.authMode "oauth") (not (and .Values.dynatrace.credentials.oauthClientId .Values.dynatrace.credentials.oauthClientSecret)) -}}
{{- fail "dynatrace.credentials.oauthClientId and oauthClientSecret are required when dynatrace.credentials.create=true and adapter.authMode=oauth" -}}
{{- end -}}
{{- end -}}
{{- end -}}
27 changes: 27 additions & 0 deletions observability-logs-dynatrace/helm/templates/adapter/configmap.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Copyright 2026 The OpenChoreo Authors
# SPDX-License-Identifier: Apache-2.0

{{- if .Values.adapter.enabled }}
apiVersion: v1
kind: ConfigMap
metadata:
name: logs-adapter-dynatrace
namespace: {{ .Release.Namespace }}
labels:
app: logs-adapter-dynatrace
data:
SERVER_PORT: "9098"
LOG_LEVEL: {{ .Values.adapter.logLevel | quote }}
DT_PLATFORM_URL: {{ trimSuffix "/" .Values.dynatrace.platformUrl | quote }}
DT_AUTH_MODE: {{ .Values.adapter.authMode | quote }}
DT_QUERY_TIMEOUT: {{ .Values.adapter.queryTimeout | quote }}
DT_ALLOW_INSECURE_HTTP: {{ .Values.adapter.allowInsecureHttp | quote }}
DT_CONTAINER_LOGS_SOURCE: {{ .Values.dynatrace.containerLogsSource | quote }}
DT_AUDIT_LOGS_SOURCE: {{ .Values.dynatrace.auditLogsSource | quote }}
DT_AUDIT_BUCKET: {{ .Values.auditLogs.bucket | quote }}
{{- if eq .Values.adapter.authMode "oauth" }}
DT_OAUTH_TOKEN_URL: {{ .Values.adapter.oauth.tokenUrl | quote }}
DT_OAUTH_SCOPE: {{ .Values.adapter.oauth.scope | quote }}
DT_OAUTH_RESOURCE: {{ .Values.adapter.oauth.resource | quote }}
{{- end }}
{{- end }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Copyright 2026 The OpenChoreo Authors
# SPDX-License-Identifier: Apache-2.0

{{- if .Values.adapter.enabled }}
{{- $c := .Values.dynatrace.credentials }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: logs-adapter-dynatrace
namespace: {{ .Release.Namespace }}
labels:
app: logs-adapter-dynatrace
spec:
replicas: 1
selector:
matchLabels:
app: logs-adapter-dynatrace
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/adapter/configmap.yaml") . | sha256sum }}
labels:
app: logs-adapter-dynatrace
spec:
securityContext:
runAsUser: 10500
runAsGroup: 10500
runAsNonRoot: true
containers:
- name: logs-adapter-dynatrace
image: "{{ .Values.adapter.image.repository }}:{{ .Values.adapter.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.adapter.image.pullPolicy | default "IfNotPresent" }}
ports:
- name: http
containerPort: 9098
envFrom:
- configMapRef:
name: logs-adapter-dynatrace
env:
{{- if eq .Values.adapter.authMode "oauth" }}
- name: DT_OAUTH_CLIENT_ID
valueFrom:
secretKeyRef:
name: {{ $c.name | quote }}
key: {{ $c.oauthClientIdKey | quote }}
- name: DT_OAUTH_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: {{ $c.name | quote }}
key: {{ $c.oauthClientSecretKey | quote }}
{{- else }}
- name: DT_PLATFORM_TOKEN
valueFrom:
secretKeyRef:
name: {{ $c.name | quote }}
key: {{ $c.platformTokenKey | quote }}
{{- end }}
readinessProbe:
httpGet:
path: /health
port: http
periodSeconds: 10
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 10
periodSeconds: 20
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
resources:
{{- toYaml .Values.adapter.resources | nindent 10 }}
{{- end }}
Loading
Loading