Skip to content

Commit 530a627

Browse files
authored
Merge branch 'main' into lambdaMicroservice
2 parents befc6e5 + 3f5301c commit 530a627

File tree

9 files changed

+424
-17
lines changed

9 files changed

+424
-17
lines changed

.github/workflows/application-signals-e2e-test.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,3 +228,12 @@ jobs:
228228
aws-region: us-east-1
229229
python-version: '3.12'
230230
caller-workflow-name: 'main-build'
231+
#
232+
# Stand-Alone ADOT/ADOT SigV4 test on EC2
233+
adot-sigv4:
234+
needs: [ upload-main-build ]
235+
uses: aws-observability/aws-application-signals-test-framework/.github/workflows/python-ec2-adot-sigv4-test.yml@main
236+
secrets: inherit
237+
with:
238+
caller-workflow-name: 'main-build'
239+
staging-wheel-name: ${{ inputs.staging-wheel-name }}

.github/workflows/release-lambda.yml

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -159,34 +159,33 @@ jobs:
159159
- name: generate layer-note
160160
working-directory: ${{ env.LAYER_NAME }}
161161
run: |
162-
echo "| Region | Layer ARN |" >> layer-note
163-
echo "| ---- | ---- |" >> layer-note
162+
echo "| Region | Layer ARN |" >> ../layer-note
163+
echo "| ---- | ---- |" >> ../layer-note
164164
for file in *
165165
do
166166
read arn < $file
167-
echo "| " $file " | " $arn " |" >> layer-note
167+
echo "| " $file " | " $arn " |" >> ../layer-note
168168
done
169-
cat layer-note
169+
cat ../layer-note
170170
- name: generate tf layer
171171
working-directory: ${{ env.LAYER_NAME }}
172172
run: |
173-
echo "locals {" >> ../layer.tf
174-
echo " sdk_layer_arns = {" >> ../layer.tf
173+
echo "locals {" >> ../layer_arns.tf
174+
echo " sdk_layer_arns = {" >> ../layer_arns.tf
175175
for file in *
176176
do
177177
read arn < $file
178-
echo " \""$file"\" = \""$arn"\"" >> ../layer.tf
178+
echo " \""$file"\" = \""$arn"\"" >> ../layer_arns.tf
179179
done
180180
cd ..
181-
echo " }" >> layer.tf
182-
echo "}" >> layer.tf
183-
terraform fmt layer.tf
184-
cat layer.tf
185-
- name: upload layer tf file
186-
uses: actions/upload-artifact@v4
181+
echo " }" >> layer_arns.tf
182+
echo "}" >> layer_arns.tf
183+
terraform fmt layer_arns.tf
184+
cat layer_arns.tf
185+
- name: download layer.zip
186+
uses: actions/download-artifact@v4
187187
with:
188-
name: layer.tf
189-
path: layer.tf
188+
name: layer.zip
190189
- name: Get commit hash
191190
id: commit
192191
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
@@ -197,7 +196,7 @@ jobs:
197196
echo "" >> release_notes.md
198197
echo "See new Lambda Layer ARNs:" >> release_notes.md
199198
echo "" >> release_notes.md
200-
cat ${{ env.LAYER_NAME }}/layer-note >> release_notes.md
199+
cat layer-note >> release_notes.md
201200
echo "" >> release_notes.md
202201
echo "Notes:" >> release_notes.md
203202
- name: Create GH release
@@ -210,6 +209,6 @@ jobs:
210209
--notes-file release_notes.md \
211210
--draft \
212211
"lambda-v${{ github.event.inputs.version }}-${{ steps.commit.outputs.sha_short }}" \
213-
layer.tf
212+
layer_arns.tf layer.zip
214213
echo Removing release_notes.md ...
215214
rm -f release_notes.md
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
name: Release ADOT OTLP UDP Exporter
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
version:
7+
description: 'Version number for deployment e.g. 0.1.0'
8+
required: true
9+
type: string
10+
11+
jobs:
12+
build-test-publish:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v3
16+
17+
- name: Set up Python
18+
uses: actions/setup-python@v4
19+
with:
20+
python-version: '3.10'
21+
22+
- name: Install dependencies
23+
run: |
24+
python -m pip install --upgrade pip
25+
pip install hatch pytest flask
26+
27+
- name: Build package
28+
working-directory: exporters/aws-otel-otlp-udp-exporter
29+
run: hatch build
30+
31+
- name: Download and run X-Ray Daemon
32+
run: |
33+
mkdir xray-daemon
34+
cd xray-daemon
35+
wget https://s3.us-west-2.amazonaws.com/aws-xray-assets.us-west-2/xray-daemon/aws-xray-daemon-linux-3.x.zip
36+
unzip aws-xray-daemon-linux-3.x.zip
37+
./xray -o -n us-west-2 -f ./daemon-logs.log --log-level debug &
38+
39+
- name: Install UDP Exporter
40+
run: |
41+
pip install ./exporters/aws-otel-otlp-udp-exporter/dist/*.whl
42+
43+
- name: Ensure Unit Tests are passing
44+
run: |
45+
pytest exporters/aws-otel-otlp-udp-exporter/tests/
46+
47+
- name: Run Sample App in Background
48+
working-directory: sample-applications/integ-test-app
49+
run: |
50+
# Start validation app
51+
python udp_exporter_validation_app.py &
52+
# Wait for validation app to initialize
53+
sleep 5
54+
55+
- name: Call Sample App Endpoint
56+
run: |
57+
echo "traceId=$(curl localhost:8080/test)" >> $GITHUB_OUTPUT
58+
59+
- name: Verify X-Ray daemon received traces
60+
run: |
61+
sleep 10
62+
echo "X-Ray daemon logs:"
63+
cat xray-daemon/daemon-logs.log
64+
65+
# Check if the daemon received and processed some data
66+
if grep -q "sending.*batch" xray-daemon/daemon-logs.log; then
67+
echo "✅ X-Ray daemon processed trace data (AWS upload errors are expected)"
68+
exit 0
69+
elif grep -q "processor:.*segment" xray-daemon/daemon-logs.log; then
70+
echo "✅ X-Ray daemon processed segment data (AWS upload errors are expected)"
71+
exit 0
72+
else
73+
echo "❌ No evidence of traces being received by X-Ray daemon"
74+
exit 1
75+
fi
76+
77+
# TODO: Steps to publish to PyPI
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
AWS OpenTelemetry OTLP UDP Exporter
2+
===================================
3+
4+
Installation
5+
------------
6+
7+
::
8+
9+
pip install aws-otel-otlp-udp-exporter
10+
11+
12+
This package provides a UDP exporter for OpenTelemetry.
13+
14+
References
15+
----------
16+
17+
* `OpenTelemetry Project <https://opentelemetry.io/>`_
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
[build-system]
2+
requires = ["hatchling"]
3+
build-backend = "hatchling.build"
4+
5+
[project]
6+
name = "aws-otel-otlp-udp-exporter"
7+
version = "0.1.0"
8+
description = "OTLP UDP Exporter for OpenTelemetry"
9+
readme = "README.rst"
10+
license = "Apache-2.0"
11+
requires-python = ">=3.8"
12+
authors = [
13+
{ name = "Amazon Web Services" }
14+
]
15+
classifiers = [
16+
"Development Status :: 4 - Beta",
17+
"Intended Audience :: Developers",
18+
"License :: OSI Approved :: Apache Software License",
19+
"Programming Language :: Python",
20+
"Programming Language :: Python :: 3",
21+
"Programming Language :: Python :: 3.8",
22+
"Programming Language :: Python :: 3.9",
23+
"Programming Language :: Python :: 3.10",
24+
"Programming Language :: Python :: 3.11",
25+
]
26+
27+
dependencies = [
28+
"opentelemetry-sdk == 1.27.0",
29+
"opentelemetry-exporter-otlp-proto-common == 1.27.0",
30+
]
31+
32+
[project.urls]
33+
Homepage = "https://github.com/aws-observability/aws-otel-python-instrumentation/tree/main/exporters/aws-otel-otlp-udp-exporter"
34+
35+
[tool.hatch.build.targets.wheel]
36+
packages = ["src/amazon"]
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
from .exporter import (
5+
DEFAULT_ENDPOINT,
6+
FORMAT_OTEL_SAMPLED_TRACES_BINARY_PREFIX,
7+
FORMAT_OTEL_UNSAMPLED_TRACES_BINARY_PREFIX,
8+
PROTOCOL_HEADER,
9+
OTLPUdpSpanExporter,
10+
UdpExporter,
11+
)
12+
13+
__all__ = [
14+
"UdpExporter",
15+
"OTLPUdpSpanExporter",
16+
"DEFAULT_ENDPOINT",
17+
"FORMAT_OTEL_SAMPLED_TRACES_BINARY_PREFIX",
18+
"FORMAT_OTEL_UNSAMPLED_TRACES_BINARY_PREFIX",
19+
"PROTOCOL_HEADER",
20+
]
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
import base64
4+
import os
5+
import socket
6+
from logging import Logger, getLogger
7+
from typing import Optional, Sequence, Tuple
8+
9+
from typing_extensions import override
10+
11+
from opentelemetry.exporter.otlp.proto.common.trace_encoder import encode_spans
12+
from opentelemetry.sdk.trace import ReadableSpan
13+
from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
14+
15+
DEFAULT_ENDPOINT = "127.0.0.1:2000"
16+
PROTOCOL_HEADER = '{"format":"json","version":1}\n'
17+
18+
FORMAT_OTEL_SAMPLED_TRACES_BINARY_PREFIX = "T1S"
19+
FORMAT_OTEL_UNSAMPLED_TRACES_BINARY_PREFIX = "T1U"
20+
21+
_logger: Logger = getLogger(__name__)
22+
23+
24+
class UdpExporter:
25+
def __init__(self, endpoint: Optional[str] = None):
26+
self._endpoint = endpoint or DEFAULT_ENDPOINT
27+
self._host, self._port = self._parse_endpoint(self._endpoint)
28+
self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
29+
self._socket.setblocking(False)
30+
31+
def send_data(self, data: bytes, signal_format_prefix: str):
32+
# base64 encoding and then converting to string with utf-8
33+
base64_encoded_string: str = base64.b64encode(data).decode("utf-8")
34+
message = f"{PROTOCOL_HEADER}{signal_format_prefix}{base64_encoded_string}"
35+
36+
try:
37+
_logger.debug("Sending UDP data: %s", message)
38+
self._socket.sendto(message.encode("utf-8"), (self._host, int(self._port)))
39+
except Exception as exc: # pylint: disable=broad-except
40+
_logger.error("Error sending UDP data: %s", exc)
41+
raise
42+
43+
def shutdown(self):
44+
self._socket.close()
45+
46+
# pylint: disable=no-self-use
47+
def _parse_endpoint(self, endpoint: str) -> Tuple[str, int]:
48+
try:
49+
vals = endpoint.split(":")
50+
host = vals[0]
51+
port = int(vals[1])
52+
except Exception as exc: # pylint: disable=broad-except
53+
raise ValueError(f"Invalid endpoint: {endpoint}") from exc
54+
55+
return host, port
56+
57+
58+
class OTLPUdpSpanExporter(SpanExporter):
59+
def __init__(self, endpoint: Optional[str] = None, sampled: bool = True):
60+
if endpoint is None and "AWS_LAMBDA_FUNCTION_NAME" in os.environ:
61+
# If in an AWS Lambda Environment, `AWS_XRAY_DAEMON_ADDRESS` will be defined
62+
endpoint = os.environ.get("AWS_XRAY_DAEMON_ADDRESS", DEFAULT_ENDPOINT)
63+
64+
self._udp_exporter = UdpExporter(endpoint=endpoint)
65+
self._sampled = sampled
66+
67+
@override
68+
def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult:
69+
serialized_data = encode_spans(spans).SerializeToString()
70+
71+
try:
72+
prefix = (
73+
FORMAT_OTEL_SAMPLED_TRACES_BINARY_PREFIX
74+
if self._sampled
75+
else FORMAT_OTEL_UNSAMPLED_TRACES_BINARY_PREFIX
76+
)
77+
self._udp_exporter.send_data(data=serialized_data, signal_format_prefix=prefix)
78+
return SpanExportResult.SUCCESS
79+
except Exception as exc: # pylint: disable=broad-except
80+
_logger.error("Error exporting spans: %s", exc)
81+
return SpanExportResult.FAILURE
82+
83+
# pylint: disable=no-self-use
84+
@override
85+
def force_flush(self, timeout_millis: int = 30000) -> bool:
86+
# TODO: implement force flush
87+
return True
88+
89+
@override
90+
def shutdown(self) -> None:
91+
self._udp_exporter.shutdown()

0 commit comments

Comments
 (0)