" python3 main.py
13 | ```
14 |
15 | Open http://localhost:8000
16 |
17 | For more details, see
18 | [Instrumenting Flask with OpenTelemetry](https://uptrace.dev/opentelemetry/instrumentations/python-flask.html)
19 |
--------------------------------------------------------------------------------
/example/flask/main.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | from flask import Flask
4 | from flask_sqlalchemy import SQLAlchemy
5 | from sqlalchemy import text
6 | from opentelemetry import trace
7 | from opentelemetry.instrumentation.flask import FlaskInstrumentor
8 | from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
9 | import uptrace
10 |
11 | uptrace.configure_opentelemetry(
12 | # Set dsn or UPTRACE_DSN env var.
13 | dsn="",
14 | service_name="myservice",
15 | service_version="1.0.0",
16 | )
17 |
18 | app = Flask(__name__)
19 | FlaskInstrumentor().instrument_app(app)
20 |
21 | app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///:memory:"
22 | db = SQLAlchemy(app)
23 | SQLAlchemyInstrumentor().instrument(engine=db.engine)
24 |
25 |
26 | @app.route("/")
27 | def index():
28 | trace_url = uptrace.trace_url()
29 |
30 | return f"""
31 |
32 | Here are some routes for you:
33 |
34 |
38 |
39 | View trace: {trace_url}
40 |
41 | """
42 |
43 |
44 | @app.route("/hello/")
45 | def hello(username):
46 | with db.engine.connect() as conn:
47 | result = conn.execute(text("select 'hello world'"))
48 | print(result.all())
49 |
50 | trace_url = uptrace.trace_url()
51 | return f"""
52 |
53 | Hello {username}
54 | {trace_url}
55 |
56 | """
57 |
58 |
59 | if __name__ == "__main__":
60 | app.run(debug=True, host="0.0.0.0", port=8000)
61 |
62 | # Send buffered spans.
63 | trace.get_tracer_provider().shutdown()
64 |
--------------------------------------------------------------------------------
/example/flask/requirements.txt:
--------------------------------------------------------------------------------
1 | uptrace==1.17.1
2 | flask==2.3.2
3 | flask_sqlalchemy==2.5.1
4 | itsdangerous==2.1.2
5 | opentelemetry-instrumentation-flask==0.38b0
6 | opentelemetry-instrumentation-sqlalchemy==0.38b0
7 |
--------------------------------------------------------------------------------
/example/metrics/Makefile:
--------------------------------------------------------------------------------
1 | all:
2 | PYTHONPATH=../../src ./main.py
3 |
--------------------------------------------------------------------------------
/example/metrics/README.md:
--------------------------------------------------------------------------------
1 | # OpenTelemetry Python metrics example
2 |
3 | To run this example, you need to install OpenTelemetry distro for Uptrace:
4 |
5 | ```shell
6 | pip install uptrace
7 | ```
8 |
9 | The run the example passing Uptrace DSN in env variables:
10 |
11 | ```shell
12 | UPTRACE_DSN="https://@uptrace.dev/" python3 main.py
13 | ```
14 |
15 | Then open Metrics tab in Uptrace.
16 |
--------------------------------------------------------------------------------
/example/metrics/main.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import time
4 | import random
5 | import threading
6 | from typing import Iterable
7 |
8 | import uptrace
9 | from opentelemetry import metrics
10 | from opentelemetry.metrics import CallbackOptions, Observation
11 |
12 | meter = metrics.get_meter("github.com/uptrace/uptrace-python", "1.0.0")
13 |
14 |
15 | def counter():
16 | counter = meter.create_counter("some.prefix.counter", description="TODO")
17 |
18 | while True:
19 | counter.add(1)
20 | time.sleep(1)
21 |
22 |
23 | def up_down_counter():
24 | counter = meter.create_up_down_counter(
25 | "some.prefix.up_down_counter", description="TODO"
26 | )
27 |
28 | while True:
29 | if random.random() >= 0.5:
30 | counter.add(+1)
31 | else:
32 | counter.add(-1)
33 | time.sleep(1)
34 |
35 |
36 | def histogram():
37 | histogram = meter.create_histogram(
38 | "some.prefix.histogram",
39 | description="TODO",
40 | unit="microseconds",
41 | )
42 |
43 | while True:
44 | histogram.record(random.randint(1, 5000000), attributes={"attr1": "value1"})
45 | time.sleep(1)
46 |
47 |
48 | def counter_observer():
49 | number = 0
50 |
51 | def callback(options: CallbackOptions) -> Iterable[Observation]:
52 | nonlocal number
53 | number += 1
54 | yield Observation(int(number), {})
55 |
56 | counter = meter.create_observable_counter(
57 | "some.prefix.counter_observer", [callback], description="TODO"
58 | )
59 |
60 |
61 | def up_down_counter_observer():
62 | def callback(options: CallbackOptions) -> Iterable[Observation]:
63 | yield Observation(random.random(), {})
64 |
65 | counter = meter.create_observable_up_down_counter(
66 | "some.prefix.up_down_counter_observer",
67 | [callback],
68 | description="TODO",
69 | )
70 |
71 |
72 | def gauge_observer():
73 | def callback(options: CallbackOptions) -> Iterable[Observation]:
74 | yield Observation(random.random(), {})
75 |
76 | gauge = meter.create_observable_gauge(
77 | "some.prefix.gauge_observer",
78 | [callback],
79 | description="TODO",
80 | )
81 |
82 |
83 | def main():
84 | # Configure OpenTelemetry with sensible defaults.
85 | uptrace.configure_opentelemetry(
86 | # Set dsn or UPTRACE_DSN env var.
87 | dsn="",
88 | service_name="myservice",
89 | service_version="1.0.0",
90 | )
91 |
92 | threading.Thread(target=counter).start()
93 | threading.Thread(target=up_down_counter).start()
94 | threading.Thread(target=histogram).start()
95 |
96 | counter_observer()
97 | up_down_counter_observer()
98 | gauge_observer()
99 |
100 | print("reporting measurements to Uptrace... (press Ctrl+C to stop)")
101 | time.sleep(300)
102 |
103 |
104 | if __name__ == "__main__":
105 | main()
106 |
--------------------------------------------------------------------------------
/example/otel-api/Makefile:
--------------------------------------------------------------------------------
1 | all:
2 | PYTHONPATH=../../src ./main.py
3 |
--------------------------------------------------------------------------------
/example/otel-api/README.md:
--------------------------------------------------------------------------------
1 | # OpenTelemetry API example
2 |
3 | This examples demonstrates how to use OpenTelemetry API with Uptrace. You can run it with:
4 |
5 | ```bash
6 | pip install -r requirements.txt
7 | UPTRACE_DSN="https://@uptrace.dev/" ./main.py
8 | ```
9 |
--------------------------------------------------------------------------------
/example/otel-api/main.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import uptrace
4 |
5 | uptrace.configure_opentelemetry(
6 | # Set dsn or UPTRACE_DSN env var.
7 | dsn="",
8 | )
9 |
10 | # Create a tracer.
11 |
12 | from opentelemetry import trace
13 |
14 | tracer = trace.get_tracer("app_or_package_name", "1.0.0")
15 |
16 | # Start a span and set some attributes.
17 |
18 | with tracer.start_as_current_span("main", kind=trace.SpanKind.SERVER) as span:
19 | if span.is_recording():
20 | span.set_attribute("key1", "value1")
21 | span.set_attributes({"key2": 123.456, "key3": [1, 2, 3]})
22 |
23 | span.add_event(
24 | "log",
25 | {
26 | "log.severity": "error",
27 | "log.message": "User not found",
28 | "enduser.id": "123",
29 | },
30 | )
31 |
32 | try:
33 | raise ValueError("error1")
34 | except ValueError as exc:
35 | span.record_exception(exc)
36 | span.set_status(trace.Status(trace.StatusCode.ERROR, str(exc)))
37 |
38 | # Current span logic.
39 |
40 | with tracer.start_as_current_span("main") as main:
41 | if trace.get_current_span() == main:
42 | print("main is active")
43 |
44 | with tracer.start_as_current_span("child") as child:
45 | if trace.get_current_span() == child:
46 | print("child is active")
47 |
48 | if trace.get_current_span() == main:
49 | print("main is active again")
50 |
51 | # Start a span and activate it manually.
52 |
53 | main = tracer.start_span("main", kind=trace.SpanKind.CLIENT)
54 |
55 | with trace.use_span(main, end_on_exit=True):
56 | if trace.get_current_span() == main:
57 | print("main is active (manually)")
58 |
--------------------------------------------------------------------------------
/example/otel-api/requirements.txt:
--------------------------------------------------------------------------------
1 | uptrace
2 |
--------------------------------------------------------------------------------
/example/otlp-logs/Makefile:
--------------------------------------------------------------------------------
1 | all:
2 | ./main.py
3 |
--------------------------------------------------------------------------------
/example/otlp-logs/README.md:
--------------------------------------------------------------------------------
1 | # Configuring OTLP logs exporter for Uptrace
2 |
3 | This example shows how to configure
4 | [OTLP](https://opentelemetry-python.readthedocs.io/en/latest/exporter/otlp/otlp.html) to export logs
5 | to Uptrace.
6 |
7 | Install dependencies:
8 |
9 | ```shell
10 | pip install -r requirements.txt
11 | ```
12 |
13 | To run this example, you need to
14 | [create an Uptrace project](https://uptrace.dev/get/get-started.html) and pass your project DSN via
15 | `UPTRACE_DSN` env variable:
16 |
17 | ```go
18 | UPTRACE_DSN=https://@api.uptrace.dev/ ./main.py
19 | ```
20 |
--------------------------------------------------------------------------------
/example/otlp-logs/main.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import os
4 | import logging
5 |
6 | import grpc
7 | from opentelemetry._logs import set_logger_provider
8 | from opentelemetry.exporter.otlp.proto.grpc._log_exporter import (
9 | OTLPLogExporter,
10 | )
11 | from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
12 | from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
13 | from opentelemetry.sdk.resources import Resource
14 |
15 | dsn = os.environ.get("UPTRACE_DSN")
16 | print("using DSN:", dsn)
17 |
18 | resource = Resource(
19 | attributes={"service.name": "myservice", "service.version": "1.0.0"}
20 | )
21 | logger_provider = LoggerProvider(resource=resource)
22 | set_logger_provider(logger_provider)
23 |
24 | exporter = OTLPLogExporter(
25 | endpoint="otlp.uptrace.dev:4317",
26 | headers=(("uptrace-dsn", dsn),),
27 | timeout=5,
28 | compression=grpc.Compression.Gzip,
29 | )
30 | logger_provider.add_log_record_processor(BatchLogRecordProcessor(exporter))
31 |
32 | handler = LoggingHandler(level=logging.NOTSET, logger_provider=logger_provider)
33 | logging.getLogger().addHandler(handler)
34 |
35 | logger = logging.getLogger("myapp.area1")
36 | logger.error("Hyderabad, we have a major problem.")
37 |
38 | logger_provider.shutdown()
39 |
--------------------------------------------------------------------------------
/example/otlp-logs/requirements.txt:
--------------------------------------------------------------------------------
1 | opentelemetry-sdk==1.24.0
2 | opentelemetry-exporter-otlp==1.24.0
3 |
--------------------------------------------------------------------------------
/example/otlp-metrics/Makefile:
--------------------------------------------------------------------------------
1 | all:
2 | ./main.py
3 |
--------------------------------------------------------------------------------
/example/otlp-metrics/README.md:
--------------------------------------------------------------------------------
1 | # Configuring OTLP metrics exporter for Uptrace
2 |
3 | This example shows how to configure
4 | [OTLP](https://opentelemetry-python.readthedocs.io/en/latest/exporter/otlp/otlp.html) to export
5 | metrics to Uptrace.
6 |
7 | Install dependencies:
8 |
9 | ```shell
10 | pip install -r requirements.txt
11 | ```
12 |
13 | To run this example, you need to
14 | [create an Uptrace project](https://uptrace.dev/get/get-started.html) and pass your project DSN via
15 | `UPTRACE_DSN` env variable:
16 |
17 | ```go
18 | UPTRACE_DSN=https://@api.uptrace.dev/ ./main.py
19 | ```
20 |
21 | To view metrics, open [app.uptrace.dev](https://app.uptrace.dev/) and navigate to the Metrics ->
22 | Explore tab.
23 |
--------------------------------------------------------------------------------
/example/otlp-metrics/main.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import os
4 | import time
5 |
6 | import grpc
7 | from opentelemetry import metrics
8 | from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import (
9 | OTLPMetricExporter,
10 | )
11 | from opentelemetry.sdk import metrics as sdkmetrics
12 | from opentelemetry.sdk.metrics import MeterProvider
13 | from opentelemetry.sdk.metrics.export import (
14 | AggregationTemporality,
15 | PeriodicExportingMetricReader,
16 | )
17 | from opentelemetry.sdk.resources import Resource
18 |
19 | dsn = os.environ.get("UPTRACE_DSN")
20 | print("using DSN:", dsn)
21 |
22 | temporality_delta = {
23 | sdkmetrics.Counter: AggregationTemporality.DELTA,
24 | sdkmetrics.UpDownCounter: AggregationTemporality.DELTA,
25 | sdkmetrics.Histogram: AggregationTemporality.DELTA,
26 | sdkmetrics.ObservableCounter: AggregationTemporality.DELTA,
27 | sdkmetrics.ObservableUpDownCounter: AggregationTemporality.DELTA,
28 | sdkmetrics.ObservableGauge: AggregationTemporality.DELTA,
29 | }
30 |
31 | exporter = OTLPMetricExporter(
32 | endpoint="otlp.uptrace.dev:4317",
33 | headers=(("uptrace-dsn", dsn),),
34 | timeout=5,
35 | compression=grpc.Compression.Gzip,
36 | preferred_temporality=temporality_delta,
37 | )
38 | reader = PeriodicExportingMetricReader(exporter)
39 |
40 | resource = Resource(
41 | attributes={"service.name": "myservice", "service.version": "1.0.0"}
42 | )
43 | provider = MeterProvider(metric_readers=[reader], resource=resource)
44 | metrics.set_meter_provider(provider)
45 |
46 | meter = metrics.get_meter("github.com/uptrace/uptrace-python", "1.0.0")
47 | counter = meter.create_counter("some.prefix.counter", description="TODO")
48 |
49 | while True:
50 | counter.add(1)
51 | time.sleep(1)
52 |
--------------------------------------------------------------------------------
/example/otlp-metrics/requirements.txt:
--------------------------------------------------------------------------------
1 | opentelemetry-sdk==1.24.0
2 | opentelemetry-exporter-otlp==1.24.0
3 |
--------------------------------------------------------------------------------
/example/otlp-traces-http/Makefile:
--------------------------------------------------------------------------------
1 | all:
2 | ./main.py
3 |
--------------------------------------------------------------------------------
/example/otlp-traces-http/README.md:
--------------------------------------------------------------------------------
1 | # Configuring OTLP traces exporter for Uptrace
2 |
3 | This example shows how to configure
4 | [OTLP](https://opentelemetry-python.readthedocs.io/en/latest/exporter/otlp/otlp.html) to export
5 | traces to Uptrace using HTTP transport.
6 |
7 | Install dependencies:
8 |
9 | ```shell
10 | pip install -r requirements.txt
11 | ```
12 |
13 | To run this example, you need to
14 | [create an Uptrace project](https://uptrace.dev/get/get-started.html) and pass your project DSN via
15 | `UPTRACE_DSN` env variable:
16 |
17 | ```go
18 | UPTRACE_DSN=https://@api.uptrace.dev/ ./main.py
19 | ```
20 |
--------------------------------------------------------------------------------
/example/otlp-traces-http/main.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import os
4 |
5 | from opentelemetry import trace
6 | from opentelemetry.sdk.resources import Resource
7 | from opentelemetry.sdk.trace import TracerProvider
8 | from opentelemetry.sdk.trace.export import BatchSpanProcessor
9 | from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
10 | OTLPSpanExporter,
11 | )
12 | from opentelemetry.exporter.otlp.proto.http import Compression
13 | from opentelemetry.sdk.extension.aws.trace import AwsXRayIdGenerator
14 |
15 | dsn = os.environ.get("UPTRACE_DSN")
16 | print("using DSN:", dsn)
17 |
18 | resource = Resource(
19 | attributes={"service.name": "myservice", "service.version": "1.0.0"}
20 | )
21 | tracer_provider = TracerProvider(
22 | resource=resource,
23 | id_generator=AwsXRayIdGenerator(),
24 | )
25 | trace.set_tracer_provider(tracer_provider)
26 |
27 | exporter = OTLPSpanExporter(
28 | endpoint="https://otlp.uptrace.dev/v1/traces",
29 | # Set the Uptrace dsn here or use UPTRACE_DSN env var.
30 | headers=(("uptrace-dsn", dsn),),
31 | timeout=10,
32 | compression=Compression.Gzip,
33 | )
34 |
35 | span_processor = BatchSpanProcessor(
36 | exporter,
37 | max_queue_size=1000,
38 | max_export_batch_size=1000,
39 | )
40 | tracer_provider.add_span_processor(span_processor)
41 |
42 | tracer = trace.get_tracer("app_or_package_name", "1.0.0")
43 |
44 | with tracer.start_as_current_span("main") as span:
45 | trace_id = span.get_span_context().trace_id
46 | print(f"trace id: {trace_id:0{32}x}")
47 |
48 | # Send buffered spans.
49 | trace.get_tracer_provider().shutdown()
50 |
--------------------------------------------------------------------------------
/example/otlp-traces-http/requirements.txt:
--------------------------------------------------------------------------------
1 | opentelemetry-sdk==1.24.0
2 | opentelemetry-exporter-otlp==1.24.0
3 | opentelemetry-sdk-extension-aws==2.0.1
4 |
--------------------------------------------------------------------------------
/example/otlp-traces/Makefile:
--------------------------------------------------------------------------------
1 | all:
2 | ./main.py
3 |
--------------------------------------------------------------------------------
/example/otlp-traces/README.md:
--------------------------------------------------------------------------------
1 | # Configuring OTLP traces exporter for Uptrace
2 |
3 | This example shows how to configure
4 | [OTLP](https://opentelemetry-python.readthedocs.io/en/latest/exporter/otlp/otlp.html) to export
5 | traces to Uptrace using gRPC transport.
6 |
7 | Install dependencies:
8 |
9 | ```shell
10 | pip install -r requirements.txt
11 | ```
12 |
13 | To run this example, you need to
14 | [create an Uptrace project](https://uptrace.dev/get/get-started.html) and pass your project DSN via
15 | `UPTRACE_DSN` env variable:
16 |
17 | ```go
18 | UPTRACE_DSN=https://@api.uptrace.dev/ ./main.py
19 | ```
20 |
--------------------------------------------------------------------------------
/example/otlp-traces/main.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import os
4 |
5 | import grpc
6 | from opentelemetry import trace
7 | from opentelemetry.sdk.resources import Resource
8 | from opentelemetry.sdk.trace import TracerProvider
9 | from opentelemetry.sdk.trace.export import BatchSpanProcessor
10 | from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
11 | OTLPSpanExporter,
12 | )
13 | from opentelemetry.sdk.extension.aws.trace import AwsXRayIdGenerator
14 |
15 | dsn = os.environ.get("UPTRACE_DSN")
16 | print("using DSN:", dsn)
17 |
18 | resource = Resource(
19 | attributes={"service.name": "myservice", "service.version": "1.0.0"}
20 | )
21 | tracer_provider = TracerProvider(
22 | resource=resource,
23 | id_generator=AwsXRayIdGenerator(),
24 | )
25 | trace.set_tracer_provider(tracer_provider)
26 |
27 | exporter = OTLPSpanExporter(
28 | endpoint="otlp.uptrace.dev:4317",
29 | # Set the Uptrace dsn here or use UPTRACE_DSN env var.
30 | headers=(("uptrace-dsn", dsn),),
31 | timeout=5,
32 | compression=grpc.Compression.Gzip,
33 | )
34 |
35 | span_processor = BatchSpanProcessor(
36 | exporter,
37 | max_queue_size=1000,
38 | max_export_batch_size=1000,
39 | )
40 | tracer_provider.add_span_processor(span_processor)
41 |
42 | tracer = trace.get_tracer("app_or_package_name", "1.0.0")
43 |
44 | with tracer.start_as_current_span("main") as span:
45 | trace_id = span.get_span_context().trace_id
46 | print(f"trace id: {trace_id:0{32}x}")
47 |
48 | # Send buffered spans.
49 | trace.get_tracer_provider().shutdown()
50 |
--------------------------------------------------------------------------------
/example/otlp-traces/requirements.txt:
--------------------------------------------------------------------------------
1 | opentelemetry-sdk==1.24.0
2 | opentelemetry-exporter-otlp==1.24.0
3 | opentelemetry-sdk-extension-aws==2.0.1
4 |
--------------------------------------------------------------------------------
/example/pyramid/Makefile:
--------------------------------------------------------------------------------
1 | all:
2 | PYTHONPATH=../../src ./main.py
3 |
4 | deps:
5 | pip install -r requirements.txt
6 |
--------------------------------------------------------------------------------
/example/pyramid/README.md:
--------------------------------------------------------------------------------
1 | # Instrumenting Pyramid with OpenTelemetry
2 |
3 | Install dependencies:
4 |
5 | ```shell
6 | pip install -r requirements.txt
7 | ```
8 |
9 | Run Pyramid app:
10 |
11 | ```shell
12 | UPTRACE_DSN="https://@uptrace.dev/" python3 main.py
13 | ```
14 |
15 | Open http://localhost:6543
16 |
17 | For more details, see
18 | [Instrumenting Pyramid with OpenTelemetry](https://uptrace.dev/opentelemetry/instrumentations/python-pyramid.html)
19 |
--------------------------------------------------------------------------------
/example/pyramid/main.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import time
4 | from opentelemetry.instrumentation.pyramid import PyramidInstrumentor
5 | from opentelemetry import trace
6 | import uptrace
7 | from wsgiref.simple_server import make_server
8 | from pyramid.config import Configurator
9 | from pyramid.response import Response
10 | from pyramid.view import view_config
11 |
12 |
13 | tracer = trace.get_tracer("mypyramid", "1.0.0")
14 |
15 |
16 | @view_config(route_name="home")
17 | def home(request):
18 | with tracer.start_as_current_span("SELECT") as span:
19 | span.set_attribute("db.system", "postgresql")
20 | span.set_attribute("db.statement", "SELECT * FROM articles LIMIT 100")
21 | time.sleep(0.1)
22 |
23 | trace_url = uptrace.trace_url()
24 | return Response(
25 | f"""
26 |
27 | Here are some routes for you:
28 |
29 |
33 |
34 | View trace: {trace_url}
35 |
36 | """
37 | )
38 |
39 |
40 | @view_config(route_name="hello")
41 | def hello(request):
42 | username = request.matchdict["username"]
43 | trace_url = uptrace.trace_url()
44 | return Response(
45 | f"""
46 |
47 | Hello {username}
48 | {trace_url}
49 |
50 | """
51 | )
52 |
53 |
54 | if __name__ == "__main__":
55 | uptrace.configure_opentelemetry(
56 | # Set dsn or UPTRACE_DSN env var.
57 | # dsn="",
58 | service_name="myservice",
59 | service_version="1.0.0",
60 | )
61 | PyramidInstrumentor().instrument()
62 |
63 | with Configurator() as config:
64 | config.add_route("home", "/")
65 | config.add_route("hello", "/hello/{username}")
66 | config.scan()
67 |
68 | app = config.make_wsgi_app()
69 |
70 | print("listening on http://localhost:6543")
71 | server = make_server("0.0.0.0", 6543, app)
72 | server.serve_forever()
73 |
--------------------------------------------------------------------------------
/example/pyramid/requirements.txt:
--------------------------------------------------------------------------------
1 | uptrace==1.16.0
2 | pyramid==2.0.2
3 | opentelemetry-instrumentation-pyramid==0.37b0
4 |
--------------------------------------------------------------------------------
/noxfile.py:
--------------------------------------------------------------------------------
1 | from nox import session
2 |
3 |
4 | @session(python=["3.8", "3.9", "3.10"], reuse_venv=True)
5 | def test(session):
6 | session.install(".")
7 | session.install("-r", "dev-requirements.txt")
8 | session.run("pytest", external=True)
9 |
10 |
11 | @session(python=["3.10"], reuse_venv=True)
12 | def lint(session):
13 | session.install(".")
14 | session.install("-r", "dev-requirements.txt")
15 | session.run("black", "src")
16 | session.run("isort", "src")
17 | session.run("pylint", "src/uptrace")
18 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | name = uptrace
3 | description = OpenTelemetry Python distribution for Uptrace
4 | long_description = file: README.md
5 | long_description_content_type = text/markdown
6 | author = Uptrace.dev
7 | author_email = support@uptrace.dev
8 | url = https://uptrace.dev
9 | platforms = any
10 | license = BSD
11 | classifiers =
12 | Development Status :: 4 - Beta
13 | Intended Audience :: Developers
14 | License :: OSI Approved :: BSD License
15 | Programming Language :: Python
16 | Programming Language :: Python :: 3
17 | Programming Language :: Python :: 3.8
18 | Typing :: Typed
19 |
20 | [options]
21 | python_requires = >=3.8
22 | package_dir=
23 | =src
24 | packages=find_namespace:
25 | zip_safe = False
26 | include_package_data = True
27 | install_requires =
28 | opentelemetry-api~=1.31.0
29 | opentelemetry-sdk~=1.31.0
30 | opentelemetry-exporter-otlp~=1.31.0
31 | opentelemetry-instrumentation~=0.52b0
32 |
33 | [options.packages.find]
34 | where = src
35 | include = *
36 |
37 | [options.entry_points]
38 | opentelemetry_distro =
39 | uptrace_distro = uptrace.distro:UptraceDistro
40 |
41 | [aliases]
42 | test=make test
43 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import setuptools
4 |
5 | PKG_INFO = {}
6 | with open(os.path.join("src", "uptrace", "version.py")) as f:
7 | exec(f.read(), PKG_INFO)
8 |
9 | setuptools.setup(version=PKG_INFO["__version__"])
10 |
--------------------------------------------------------------------------------
/src/uptrace/__init__.py:
--------------------------------------------------------------------------------
1 | """Uptrace exporter for OpenTelemetry"""
2 |
3 | from .dsn import parse_dsn
4 | from .uptrace import (
5 | configure_opentelemetry,
6 | force_flush,
7 | report_exception,
8 | shutdown,
9 | trace_url,
10 | )
11 | from .version import __version__
12 |
13 | __all__ = [
14 | "configure_opentelemetry",
15 | "force_flush",
16 | "shutdown",
17 | "trace_url",
18 | "report_exception",
19 | "parse_dsn",
20 | "__version__",
21 | ]
22 |
--------------------------------------------------------------------------------
/src/uptrace/client.py:
--------------------------------------------------------------------------------
1 | """Uptrace client for Python"""
2 |
3 | from typing import Optional
4 |
5 | from opentelemetry import trace
6 |
7 | from .dsn import DSN
8 |
9 | DUMMY_SPAN_NAME = "__dummy__"
10 |
11 |
12 | class Client:
13 | """Uptrace client for Python"""
14 |
15 | def __init__(self, dsn: DSN):
16 | self._dsn = dsn
17 | self._tracer = None
18 |
19 | def report_exception(self, exc: Exception) -> None:
20 | """Reports an exception as a span event creating a dummy span if necessary."""
21 |
22 | span = trace.get_current_span()
23 | if span.is_recording():
24 | span.record_exception(exc)
25 | return
26 |
27 | span = self._get_tracer().start_span(DUMMY_SPAN_NAME)
28 | span.record_exception(exc)
29 | span.end()
30 |
31 | def _get_tracer(self):
32 | if self._tracer is None:
33 | self._tracer = trace.get_tracer("uptrace-python")
34 | return self._tracer
35 |
36 | def trace_url(self, span: Optional[trace.Span] = None) -> str:
37 | if span is None:
38 | span = trace.get_current_span()
39 | span_ctx = span.get_span_context()
40 | trace_id = span_ctx.trace_id
41 | span_id = span_ctx.span_id
42 | return f"{self._dsn.site_url}/traces/{trace_id:0{32}x}?span_id={span_id}"
43 |
--------------------------------------------------------------------------------
/src/uptrace/distro.py:
--------------------------------------------------------------------------------
1 | from opentelemetry.instrumentation.distro import BaseDistro
2 |
3 | from .uptrace import configure_opentelemetry
4 |
5 |
6 | # pylint: disable=too-few-public-methods
7 | class UptraceDistro(BaseDistro):
8 | def _configure(self, **kwargs):
9 | configure_opentelemetry()
10 |
--------------------------------------------------------------------------------
/src/uptrace/dsn.py:
--------------------------------------------------------------------------------
1 | from urllib.parse import parse_qs, urlparse
2 |
3 |
4 | class DSN:
5 | # pylint:disable=too-many-arguments
6 | def __init__(
7 | self, dsn="", scheme="", host="", http_port="", grpc_port="", token=""
8 | ):
9 | self.str = dsn
10 | self.scheme = scheme
11 | self.host = host
12 | self.http_port = http_port
13 | self.grpc_port = grpc_port
14 | self.token = token
15 |
16 | def __str__(self):
17 | return self.str
18 |
19 | @property
20 | def site_url(self):
21 | if self.host == "uptrace.dev":
22 | return "https://app.uptrace.dev"
23 | if self.http_port:
24 | return f"{self.scheme}://{self.host}:{self.http_port}"
25 | return f"{self.scheme}://{self.host}"
26 |
27 | @property
28 | def otlp_http_endpoint(self):
29 | if self.host == "uptrace.dev":
30 | return "https://api.uptrace.dev"
31 | if self.http_port:
32 | return f"{self.scheme}://{self.host}:{self.http_port}"
33 | return f"{self.scheme}://{self.host}"
34 |
35 | @property
36 | def otlp_grpc_endpoint(self):
37 | if self.host == "uptrace.dev":
38 | return "https://api.uptrace.dev:4317"
39 | if self.grpc_port:
40 | return f"{self.scheme}://{self.host}:{self.grpc_port}"
41 | return f"{self.scheme}://{self.host}"
42 |
43 |
44 | def parse_dsn(dsn: str) -> DSN:
45 | if dsn == "":
46 | raise ValueError("either dsn option or UPTRACE_DSN is required")
47 |
48 | o = urlparse(dsn)
49 | if not o.scheme:
50 | raise ValueError(f"can't parse DSN={dsn}")
51 |
52 | host = o.hostname
53 | if not host:
54 | raise ValueError(f"DSN={dsn} does not have a host")
55 | if host == "api.uptrace.dev":
56 | host = "uptrace.dev"
57 |
58 | token = o.username
59 | grpc_port = "14317"
60 | if o.query:
61 | query = parse_qs(o.query)
62 | if "grpc" in query:
63 | grpc_port = query["grpc"][0]
64 |
65 | return DSN(
66 | dsn=dsn,
67 | scheme=o.scheme,
68 | host=host,
69 | http_port=o.port,
70 | grpc_port=grpc_port,
71 | token=token,
72 | )
73 |
--------------------------------------------------------------------------------
/src/uptrace/id_generator.py:
--------------------------------------------------------------------------------
1 | import random
2 | import time
3 |
4 | from opentelemetry.sdk.trace.id_generator import IdGenerator
5 |
6 |
7 | class UptraceIdGenerator(IdGenerator):
8 | def generate_span_id(self) -> int:
9 | return random.getrandbits(64)
10 |
11 | def generate_trace_id(self) -> int:
12 | high = int(time.time() * 1e9)
13 | low = random.getrandbits(64)
14 | return (high << 64) | low
15 |
--------------------------------------------------------------------------------
/src/uptrace/logs.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | import grpc
4 | from opentelemetry._logs import set_logger_provider
5 | from opentelemetry.exporter.otlp.proto.grpc._log_exporter import (
6 | OTLPLogExporter,
7 | )
8 | from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
9 | from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
10 | from opentelemetry.sdk.resources import Resource
11 |
12 | from .dsn import DSN
13 |
14 |
15 | def configure_logs(dsn: DSN, resource: Resource, level=logging.NOTSET):
16 | logger_provider = LoggerProvider(resource=resource)
17 | set_logger_provider(logger_provider)
18 |
19 | exporter = OTLPLogExporter(
20 | endpoint=dsn.otlp_grpc_endpoint,
21 | headers=(("uptrace-dsn", dsn.str),),
22 | timeout=5,
23 | compression=grpc.Compression.Gzip,
24 | )
25 | logger_provider.add_log_record_processor(BatchLogRecordProcessor(exporter))
26 |
27 | handler = LoggingHandler(level=level, logger_provider=logger_provider)
28 | logging.getLogger().addHandler(handler)
29 |
--------------------------------------------------------------------------------
/src/uptrace/metrics.py:
--------------------------------------------------------------------------------
1 | import grpc
2 | from opentelemetry import metrics
3 | from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import (
4 | OTLPMetricExporter,
5 | )
6 | from opentelemetry.sdk import metrics as sdkmetrics
7 | from opentelemetry.sdk.metrics import MeterProvider
8 | from opentelemetry.sdk.metrics.export import (
9 | AggregationTemporality,
10 | PeriodicExportingMetricReader,
11 | )
12 | from opentelemetry.sdk.metrics.view import (
13 | ExponentialBucketHistogramAggregation,
14 | )
15 | from opentelemetry.sdk.resources import Resource
16 |
17 | from .dsn import DSN
18 |
19 | temporality_delta = {
20 | sdkmetrics.Counter: AggregationTemporality.DELTA,
21 | sdkmetrics.UpDownCounter: AggregationTemporality.DELTA,
22 | sdkmetrics.Histogram: AggregationTemporality.DELTA,
23 | sdkmetrics.ObservableCounter: AggregationTemporality.DELTA,
24 | sdkmetrics.ObservableUpDownCounter: AggregationTemporality.DELTA,
25 | sdkmetrics.ObservableGauge: AggregationTemporality.DELTA,
26 | }
27 |
28 |
29 | def configure_metrics(
30 | dsn: DSN,
31 | resource: Resource,
32 | ):
33 | exporter = OTLPMetricExporter(
34 | endpoint=dsn.otlp_grpc_endpoint,
35 | headers=(("uptrace-dsn", dsn.str),),
36 | timeout=5,
37 | compression=grpc.Compression.Gzip,
38 | preferred_temporality=temporality_delta,
39 | preferred_aggregation={
40 | sdkmetrics.Histogram: ExponentialBucketHistogramAggregation()
41 | },
42 | )
43 | reader = PeriodicExportingMetricReader(exporter)
44 | provider = MeterProvider(metric_readers=[reader], resource=resource)
45 | metrics.set_meter_provider(provider)
46 |
--------------------------------------------------------------------------------
/src/uptrace/traces.py:
--------------------------------------------------------------------------------
1 | import grpc
2 | from opentelemetry import trace
3 | from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
4 | OTLPSpanExporter,
5 | )
6 | from opentelemetry.sdk.resources import Resource
7 | from opentelemetry.sdk.trace import TracerProvider
8 | from opentelemetry.sdk.trace.export import BatchSpanProcessor
9 |
10 | from .dsn import DSN
11 | from .id_generator import UptraceIdGenerator
12 |
13 |
14 | def configure_traces(
15 | dsn: DSN,
16 | resource: Resource,
17 | ):
18 | provider = TracerProvider(resource=resource, id_generator=UptraceIdGenerator())
19 | trace.set_tracer_provider(provider)
20 |
21 | exporter = OTLPSpanExporter(
22 | endpoint=dsn.otlp_grpc_endpoint,
23 | headers=(("uptrace-dsn", dsn.str),),
24 | timeout=5,
25 | compression=grpc.Compression.Gzip,
26 | )
27 |
28 | bsp = BatchSpanProcessor(
29 | exporter,
30 | max_queue_size=1000,
31 | max_export_batch_size=1000,
32 | schedule_delay_millis=5000,
33 | )
34 | trace.get_tracer_provider().add_span_processor(bsp)
35 |
--------------------------------------------------------------------------------
/src/uptrace/uptrace.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import os
3 | from socket import gethostname
4 | from typing import Optional
5 |
6 | from opentelemetry import _logs, metrics, trace
7 | from opentelemetry.sdk.resources import Attributes, Resource
8 |
9 | from .client import Client
10 | from .dsn import parse_dsn
11 | from .logs import configure_logs
12 | from .metrics import configure_metrics
13 | from .traces import configure_traces
14 |
15 | logger = logging.getLogger(__name__)
16 |
17 | _CLIENT = Client(parse_dsn("https://@api.uptrace.dev"))
18 |
19 |
20 | # pylint: disable=too-many-arguments
21 | def configure_opentelemetry(
22 | dsn="",
23 | service_name: Optional[str] = "",
24 | service_version: Optional[str] = "",
25 | deployment_environment: Optional[str] = "",
26 | resource_attributes: Optional[Attributes] = None,
27 | resource: Optional[Resource] = None,
28 | logging_level=logging.NOTSET,
29 | ):
30 | """
31 | configure_opentelemetry configures OpenTelemetry to export data to Uptrace.
32 | By default it:
33 | - Creates tracer provider.
34 | - Registers OTLP span exporter.
35 | - Creates metrics provider.
36 | - Registers OTLP metrics exporter.
37 | """
38 |
39 | global _CLIENT # pylint: disable=global-statement
40 |
41 | if os.getenv("UPTRACE_DISABLED") == "True":
42 | logger.info("UPTRACE_DISABLED=True: Uptrace is disabled")
43 | return
44 |
45 | if not dsn:
46 | dsn = os.getenv("UPTRACE_DSN", "")
47 |
48 | try:
49 | dsn = parse_dsn(dsn)
50 | except ValueError as exc:
51 | # pylint:disable=logging-not-lazy
52 | logger.warning("can't parse Uptrace DSN: %s (Uptrace is disabled)", exc)
53 | return
54 |
55 | if dsn.token == "":
56 | logger.warning("dummy Uptrace DSN detected: %s (Uptrace is disabled)", dsn)
57 | return
58 |
59 | if dsn.grpc_port == "14318":
60 | logger.warning(
61 | "uptrace-python uses OTLP/gRPC exporter, but got port %s", dsn.port
62 | )
63 |
64 | resource = _build_resource(
65 | resource,
66 | resource_attributes,
67 | service_name,
68 | service_version,
69 | deployment_environment,
70 | )
71 |
72 | _CLIENT = Client(dsn=dsn)
73 | configure_traces(dsn, resource=resource)
74 | configure_metrics(dsn, resource=resource)
75 | configure_logs(dsn, resource=resource, level=logging_level)
76 |
77 |
78 | def shutdown():
79 | trace.get_tracer_provider().shutdown()
80 | metrics.get_meter_provider().shutdown()
81 | _logs.get_logger_provider().shutdown()
82 |
83 |
84 | def force_flush():
85 | trace.get_tracer_provider().force_flush()
86 | metrics.get_meter_provider().force_flush()
87 | _logs.get_logger_provider().force_flush()
88 |
89 |
90 | def trace_url(span: Optional[trace.Span] = None) -> str:
91 | """Returns the trace URL for the span."""
92 |
93 | return _CLIENT.trace_url(span)
94 |
95 |
96 | def report_exception(exc: Exception) -> None:
97 | if _CLIENT is not None:
98 | _CLIENT.report_exception(exc)
99 |
100 |
101 | def _build_resource(
102 | resource: Resource,
103 | resource_attributes: Attributes,
104 | service_name: str,
105 | service_version: str,
106 | deployment_environment: str,
107 | ) -> Resource:
108 | if resource:
109 | return resource
110 |
111 | attrs = {"host.name": gethostname()}
112 |
113 | if resource_attributes:
114 | attrs.update(resource_attributes)
115 | if service_name:
116 | attrs["service.name"] = service_name
117 | if service_version:
118 | attrs["service.version"] = service_version
119 | if deployment_environment:
120 | attrs["deployment.environment"] = deployment_environment
121 |
122 | return Resource.create(attrs)
123 |
--------------------------------------------------------------------------------
/src/uptrace/util.py:
--------------------------------------------------------------------------------
1 | def remove_prefix(s: str, prefix: str) -> str:
2 | return s[len(prefix) :] if s.startswith(prefix) else s
3 |
--------------------------------------------------------------------------------
/src/uptrace/version.py:
--------------------------------------------------------------------------------
1 | """Uptrace distro version"""
2 |
3 | __version__ = "1.31.0"
4 |
--------------------------------------------------------------------------------
/test/test_uptrace.py:
--------------------------------------------------------------------------------
1 | from unittest.mock import patch
2 |
3 | import pytest
4 | from opentelemetry import trace as trace
5 | from opentelemetry.sdk.trace import TracerProvider
6 | from opentelemetry.trace.status import StatusCode
7 |
8 | import uptrace
9 |
10 |
11 | def setup_function():
12 | trace._TRACER_PROVIDER = TracerProvider()
13 |
14 |
15 | def teardown_function():
16 | trace._TRACER_PROVIDER = None
17 |
18 |
19 | def test_span_processor_no_dsn(caplog):
20 | uptrace.configure_opentelemetry()
21 | assert "either dsn option or UPTRACE_DSN is required" in caplog.text
22 |
23 |
24 | def test_span_processor_invalid_dsn(caplog):
25 | uptrace.configure_opentelemetry(dsn="invalid")
26 | assert "can't parse DSN=invalid" in caplog.text
27 |
28 |
29 | def test_trace_url():
30 | tracer = trace.get_tracer("tracer_name")
31 | span = tracer.start_span("main span")
32 |
33 | url = uptrace.trace_url(span)
34 | assert url.startswith("https://app.uptrace.dev/traces/")
35 |
36 |
37 | def test_dsn():
38 | dsn = uptrace.parse_dsn("http://localhost:14318")
39 | assert dsn.site_url == "http://localhost:14318"
40 | assert dsn.otlp_http_endpoint == "http://localhost:14318"
41 | assert dsn.otlp_grpc_endpoint == "http://localhost:14317"
42 |
43 | dsn = uptrace.parse_dsn("https://localhost?grpc=123")
44 | assert dsn.site_url == "https://localhost"
45 | assert dsn.otlp_http_endpoint == "https://localhost"
46 | assert dsn.otlp_grpc_endpoint == "https://localhost:123"
47 |
48 | dsn = uptrace.parse_dsn("https://@uptrace.dev/")
49 | assert dsn.site_url == "https://app.uptrace.dev"
50 | assert dsn.otlp_http_endpoint == "https://api.uptrace.dev"
51 | assert dsn.otlp_grpc_endpoint == "https://api.uptrace.dev:4317"
52 |
--------------------------------------------------------------------------------