Skip to content

Commit a05a651

Browse files
authored
Merge branch 'GoogleCloudPlatform:main' into main
2 parents 34a98ff + 5f53472 commit a05a651

File tree

13 files changed

+1239
-508
lines changed

13 files changed

+1239
-508
lines changed
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
FROM python:3.12
1+
FROM python:3.12-slim
2+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
23
WORKDIR /usr/src/app
34
COPY . .
4-
RUN python -m venv /venv && \
5-
/venv/bin/pip install -r requirements.txt
6-
CMD /venv/bin/gunicorn -b 0.0.0.0:8080 -w 4 'app:app' 2>&1 | tee /var/log/app.log
5+
RUN uv sync --frozen
6+
CMD uv run gunicorn -b 0.0.0.0:8080 -w 4 'app:app' 2>&1 | tee /var/log/app.log

samples/instrumentation-quickstart/README.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
# OpenTelemetry Python instrumentation example
22

3-
**This example is under development, and does not currently work**
4-
5-
This sample is a Python application instrumented with TODO. This is a python version
3+
This sample is a Python application instrumented with OpenTelemetry. This is a python version
64
of [this golang
75
sample](https://github.com/GoogleCloudPlatform/golang-samples/tree/main/opentelemetry/instrumentation).
86
It uses docker compose to run the application and send it some requests.
@@ -85,4 +83,4 @@ After a successful run of the example, you can see the generated metrics in the
8583
Similarly, to view the generated traces in the GCP console, use the Trace Explorer.
8684

8785
[auth_command]: https://cloud.google.com/sdk/gcloud/reference/beta/auth/application-default/login
88-
[ADC]: https://cloud.google.com/docs/authentication/application-default-credentials
86+
[ADC]: https://cloud.google.com/docs/authentication/application-default-credentials

samples/instrumentation-quickstart/app.py

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,41 +12,55 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
from random import randint, uniform
16-
import time
1715
import logging
16+
import time
17+
from random import randint, uniform
18+
1819
import requests
1920
from flask import Flask, url_for
20-
import setup_opentelemetry
21-
import gcp_logging
22-
23-
from opentelemetry.instrumentation.requests import RequestsInstrumentor
2421
from opentelemetry.instrumentation.flask import FlaskInstrumentor
22+
from opentelemetry.instrumentation.requests import RequestsInstrumentor
23+
24+
from gcp_logging import setup_structured_logging
25+
from setup_opentelemetry import setup_opentelemetry
2526

2627
# [START opentelemetry_instrumentation_main]
2728
logger = logging.getLogger(__name__)
2829

30+
# Initialize OpenTelemetry Python SDK and structured logging
31+
setup_opentelemetry()
32+
setup_structured_logging()
33+
2934
app = Flask(__name__)
35+
36+
# Add instrumentation
3037
FlaskInstrumentor().instrument_app(app)
3138
RequestsInstrumentor().instrument()
3239
# [END opentelemetry_instrumentation_main]
3340

41+
3442
# [START opentelemetry_instrumentation_handle_multi]
35-
@app.route('/multi')
43+
@app.route("/multi")
3644
def multi():
3745
"""Handle an http request by making 3-7 http requests to the /single endpoint."""
38-
subRequests = randint(3, 7)
39-
logger.info("handle /multi request", extra={'subRequests': subRequests})
40-
for _ in range(subRequests):
41-
requests.get(url_for('single', _external=True))
42-
return 'ok'
46+
sub_requests = randint(3, 7)
47+
logger.info("handle /multi request", extra={"subRequests": sub_requests})
48+
for _ in range(sub_requests):
49+
requests.get(url_for("single", _external=True))
50+
return "ok"
51+
52+
4353
# [END opentelemetry_instrumentation_handle_multi]
4454

55+
4556
# [START opentelemetry_instrumentation_handle_single]
46-
@app.route('/single')
57+
@app.route("/single")
4758
def single():
4859
"""Handle an http request by sleeping for 100-200 ms, and write the number of seconds slept as the response."""
4960
duration = uniform(0.1, 0.2)
61+
logger.info("handle /single request", extra={"duration": duration})
5062
time.sleep(duration)
51-
return f'slept {duration} seconds'
63+
return f"slept {duration} seconds"
64+
65+
5266
# [END opentelemetry_instrumentation_handle_single]

samples/instrumentation-quickstart/gcp_logging.py

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,44 @@
1313
# limitations under the License.
1414

1515
import logging
16-
from pythonjsonlogger import jsonlogger
16+
from datetime import datetime
17+
from typing import Optional
18+
1719
from opentelemetry.instrumentation.logging import LoggingInstrumentor
20+
from pythonjsonlogger import jsonlogger
21+
22+
23+
# We override JsonFormatter.formatTime() instead of using the datefmt strftime parameter
24+
# because it does not support microsecond precision.
25+
1826

1927
# [START opentelemetry_instrumentation_setup_logging]
20-
LoggingInstrumentor().instrument()
21-
22-
logHandler = logging.StreamHandler()
23-
formatter = jsonlogger.JsonFormatter(
24-
"%(asctime)s %(levelname)s %(message)s %(otelTraceID)s %(otelSpanID)s %(otelTraceSampled)s",
25-
rename_fields={
26-
"levelname": "severity",
27-
"asctime": "timestamp",
28-
"otelTraceID": "logging.googleapis.com/trace",
29-
"otelSpanID": "logging.googleapis.com/spanId",
30-
"otelTraceSampled": "logging.googleapis.com/trace_sampled",
28+
class JsonFormatter(jsonlogger.JsonFormatter):
29+
def formatTime(self, record: logging.LogRecord, datefmt: Optional[str] = None):
30+
# Format the timestamp as RFC 3339 with microsecond precision
31+
isoformat = datetime.fromtimestamp(record.created).isoformat()
32+
return f"{isoformat}Z"
33+
34+
35+
def setup_structured_logging() -> None:
36+
LoggingInstrumentor().instrument()
37+
38+
log_handler = logging.StreamHandler()
39+
formatter = JsonFormatter(
40+
"%(asctime)s %(levelname)s %(message)s %(otelTraceID)s %(otelSpanID)s %(otelTraceSampled)s",
41+
rename_fields={
42+
"levelname": "severity",
43+
"asctime": "timestamp",
44+
"otelTraceID": "logging.googleapis.com/trace",
45+
"otelSpanID": "logging.googleapis.com/spanId",
46+
"otelTraceSampled": "logging.googleapis.com/trace_sampled",
3147
},
32-
datefmt="%Y-%m-%dT%H:%M:%SZ",
33-
)
34-
logHandler.setFormatter(formatter)
35-
logging.basicConfig(
36-
level=logging.INFO,
37-
handlers=[logHandler],
38-
)
48+
)
49+
log_handler.setFormatter(formatter)
50+
logging.basicConfig(
51+
level=logging.INFO,
52+
handlers=[log_handler],
53+
)
54+
55+
3956
# [END opentelemetry_instrumentation_setup_logging]

0 commit comments

Comments
 (0)