Skip to content
Merged
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
9 changes: 9 additions & 0 deletions .env.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# OTEL
OTEL_SDK_DISABLED=false
OTEL_ENABLE_FLASK=true
OTEL_ENABLE_LOGGING=true
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317
OTEL_EXPORTER_OTLP_INSECURE=true
OTEL_RESOURCE_ATTRIBUTES=service.name=service-alti
OTEL_PYTHON_EXCLUDED_URLS=""
OTEL_PYTHON_LOG_CORRELATION=true
4 changes: 0 additions & 4 deletions .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,6 @@ load-plugins=pylint_flask
# Pickle collected data for later comparisons.
persistent=yes

# When enabled, pylint would attempt to guess common misconfiguration and emit
# user-friendly hints instead of false-positive error messages.
suggestion-mode=yes

# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no
Expand Down
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ ci:
# Create virtual env with all packages for development using the Pipfile.lock
pipenv sync --dev

.PHONY: otelrequirements
otelrequirements:
edot-bootstrap --action=requirements

# linting target, calls upon yapf to make sure your code is easier to read and respects some conventions.

Expand Down
6 changes: 6 additions & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ logging-utilities = "~=5.0"
Flask = "~=3.1"
PyYAML = "~=6.0"

# OpenTelemetry packages
Comment thread
ltflb-bgdi marked this conversation as resolved.
opentelemetry-sdk = "~=1.40"
opentelemetry-exporter-otlp = "~=1.40"
opentelemetry-instrumentation-flask = "*"
opentelemetry-instrumentation-logging = "*"

[dev-packages]
mock = "*"
nose2 = "*"
Expand Down
1,375 changes: 952 additions & 423 deletions Pipfile.lock

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions app/helpers/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,17 @@ def filter_coordinate(coordinate):
"""Returns the coordinate given in parameter, rounder three decimal places"""
# 1mm accuracy is enough for coordinates
return round(coordinate, 3)


def strtobool(value) -> bool:
"""Convert a string representation of truth to true (1) or false (0).
True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if
'val' is anything else.
"""
value = value.lower()
if value in ('y', 'yes', 't', 'true', 'on', '1'):
return True
if value in ('n', 'no', 'f', 'false', 'off', '0'):
return False
raise ValueError(f"invalid truth value \'{value}\'")
41 changes: 41 additions & 0 deletions app/helpers/otel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from os import getenv

from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import \
OTLPSpanExporter
from opentelemetry.instrumentation.flask import FlaskInstrumentor
from opentelemetry.instrumentation.logging import LoggingInstrumentor
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

from app.helpers.helpers import strtobool


def initialize() -> None:
if not strtobool(getenv("OTEL_SDK_DISABLED", "false")):
if strtobool(getenv("OTEL_ENABLE_LOGGING", "false")):
LoggingInstrumentor().instrument()


def initialize_flask(app):
if not strtobool(getenv("OTEL_SDK_DISABLED", "false")):
if strtobool(getenv("OTEL_ENABLE_FLASK", "false")):
FlaskInstrumentor().instrument_app(app)


def setup_trace_provider():
if not strtobool(getenv("OTEL_SDK_DISABLED", "false")):
# Since we created a new tracer, the default span processor is gone. We need to
# create a new one using the default OTEL env variables and ad it to the tracer.
span_processor = BatchSpanProcessor(
OTLPSpanExporter(
endpoint=getenv('OTEL_EXPORTER_OTLP_ENDPOINT', "http://localhost:4317"),
headers=getenv('OTEL_EXPORTER_OTLP_HEADERS'),
insecure=strtobool(getenv('OTEL_EXPORTER_OTLP_INSECURE', "false"))
)
)

provider = TracerProvider(resource=Resource.create())
provider.add_span_processor(span_processor)
trace.set_tracer_provider(provider)
30 changes: 30 additions & 0 deletions docker-compose-otel.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
version: "2"
services:
service-alti:
build: .
environment:
- HTTP_PORT=8080
- LOGS_DIR=/logs
- DTM_BASE_PATH=/var/local/profile
env_file:
- .env.test
ports:
- "8080:8080"
volumes:
- ./logs:/logs
- /var/local/profile:/var/local/profile
depends_on:
- otel-collector

otel-collector:
image: otel/opentelemetry-collector-contrib:latest
command: --config otel-local-config.yaml
volumes:
- ./otel-local-config.yaml:/otel-local-config.yaml
ports:
- "4317:4317"

zipkin:
image: openzipkin/zipkin:latest
ports:
- "9411:9411"
3 changes: 3 additions & 0 deletions logging-cfg-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ formatters:
duration: "%(duration)s"
payload: "%(response.json).128s"
message: message
otelSpanID: otelSpanID
otelTraceID: otelTraceID
otelTraceSampled: otelTraceSampled

handlers:
console:
Expand Down
21 changes: 21 additions & 0 deletions otel-local-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317

processors:
batch:

exporters:
debug:
verbosity: detailed
zipkin:
endpoint: http://zipkin:9411/api/v2/spans

service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [debug, zipkin]
25 changes: 24 additions & 1 deletion wsgi.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
# Initialize OTEL.
# Initialize should be called as early as possible, but at least before the app is imported
# The order has a impact on how the libraries are instrumented. If called after app import,
# e.g. the flask instrumentation has no effect. See:
# https://github.com/open-telemetry/opentelemetry.io/blob/main/content/en/docs/zero-code/python/troubleshooting.md#use-programmatic-auto-instrumentation
# pylint: disable=wrong-import-position,wrong-import-order,ungrouped-imports

from app.helpers.otel import initialize
from app.helpers.otel import initialize_flask
from app.helpers.otel import setup_trace_provider

initialize()

import multiprocessing

from gunicorn.app.base import BaseApplication
Expand All @@ -9,6 +22,15 @@
from app.settings import GUNICORN_WORKER_TMP_DIR
from app.settings import HTTP_PORT

initialize_flask(application)


def post_fork(server, worker):
server.log.info("Worker spawned (pid: %s)", worker.pid)

# Setup OTEL providers for this worker
setup_trace_provider()


class StandaloneApplication(BaseApplication): # pylint: disable=abstract-method

Expand Down Expand Up @@ -44,6 +66,7 @@ def load(self):
'worker_tmp_dir': GUNICORN_WORKER_TMP_DIR,
'timeout': 60,
'keepalive': GUNICORN_KEEPALIVE,
'logconfig_dict': get_logging_cfg()
'logconfig_dict': get_logging_cfg(),
'post_fork': post_fork
}
StandaloneApplication(application, options).run()