Skip to content

Commit 94ad656

Browse files
ps48julianocosta89
andauthored
[load-generator] Update locustfile for logging with TraceContext (#2265)
* update locustfile for logging with traceContext Signed-off-by: Shenoy Pratik <pshenoy36@gmail.com> * update changelog, lint check Signed-off-by: Shenoy Pratik <pshenoy36@gmail.com> * remove explicitly adding service name, remove double logging Signed-off-by: Shenoy Pratik <pshenoy36@gmail.com> * Update src/load-generator/locustfile.py Co-authored-by: Juliano Costa <julianocosta89@outlook.com> * Update src/load-generator/locustfile.py Co-authored-by: Juliano Costa <julianocosta89@outlook.com> --------- Signed-off-by: Shenoy Pratik <pshenoy36@gmail.com> Co-authored-by: Juliano Costa <julianocosta89@outlook.com>
1 parent 6068756 commit 94ad656

File tree

3 files changed

+121
-68
lines changed

3 files changed

+121
-68
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ the release.
3737
([2214](https://github.com/open-telemetry/opentelemetry-demo/pull/2214))
3838
* [quote] replace debian image with latest alpine image
3939
([2216](https://github.com/open-telemetry/opentelemetry-demo/pull/2216))
40+
* [load-generator] Update locustfile for logging with TraceContext
41+
([2265](https://github.com/open-telemetry/opentelemetry-demo/pull/2265))
4042

4143
## 2.0.2
4244

src/load-generator/locustfile.py

Lines changed: 118 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from opentelemetry.instrumentation.requests import RequestsInstrumentor
2626
from opentelemetry.instrumentation.system_metrics import SystemMetricsInstrumentor
2727
from opentelemetry.instrumentation.urllib3 import URLLib3Instrumentor
28+
from opentelemetry.instrumentation.logging import LoggingInstrumentor
2829
from opentelemetry._logs import set_logger_provider
2930
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import (
3031
OTLPLogExporter,
@@ -39,34 +40,41 @@
3940

4041
from playwright.async_api import Route, Request
4142

42-
logger_provider = LoggerProvider(resource=Resource.create(
43-
{
44-
"service.name": "load-generator",
45-
}
46-
),)
43+
# Configure tracer provider first (needed for trace context in logs)
44+
tracer_provider = TracerProvider()
45+
trace.set_tracer_provider(tracer_provider)
46+
tracer_provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter(insecure=True)))
47+
48+
# Configure logger provider with the same resource
49+
logger_provider = LoggerProvider()
4750
set_logger_provider(logger_provider)
4851

49-
exporter = OTLPLogExporter(insecure=True)
50-
logger_provider.add_log_record_processor(BatchLogRecordProcessor(exporter))
52+
# Set up log exporter and processor
53+
log_exporter = OTLPLogExporter(insecure=True)
54+
logger_provider.add_log_record_processor(BatchLogRecordProcessor(log_exporter))
55+
56+
# Create logging handler that will include trace context
5157
handler = LoggingHandler(level=logging.INFO, logger_provider=logger_provider)
5258

53-
# Attach OTLP handler to locust logger
54-
logging.getLogger().addHandler(handler)
55-
logging.getLogger().setLevel(logging.INFO)
59+
# Configure root logger
60+
root_logger = logging.getLogger()
61+
root_logger.addHandler(handler)
62+
root_logger.setLevel(logging.INFO)
5663

57-
exporter = OTLPMetricExporter(insecure=True)
58-
set_meter_provider(MeterProvider([PeriodicExportingMetricReader(exporter)]))
64+
# Configure metrics
65+
metric_exporter = OTLPMetricExporter(insecure=True)
66+
set_meter_provider(MeterProvider([PeriodicExportingMetricReader(metric_exporter)]))
5967

60-
tracer_provider = TracerProvider()
61-
trace.set_tracer_provider(tracer_provider)
62-
tracer_provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
68+
# Instrument logging to automatically inject trace context
69+
LoggingInstrumentor().instrument(set_logging_format=True)
6370

6471
# Instrumenting manually to avoid error with locust gevent monkey
6572
Jinja2Instrumentor().instrument()
6673
RequestsInstrumentor().instrument()
6774
SystemMetricsInstrumentor().instrument()
6875
URLLib3Instrumentor().instrument()
69-
logging.info("Instrumentation complete")
76+
77+
logging.info("Instrumentation complete - logs will now include trace context")
7078

7179
# Initialize Flagd provider
7280
base_url = f"http://{os.environ.get('FLAGD_HOST', 'localhost')}:{os.environ.get('FLAGD_OFREP_PORT', 8016)}"
@@ -107,76 +115,110 @@ def get_flagd_value(FlagName):
107115
class WebsiteUser(HttpUser):
108116
wait_time = between(1, 10)
109117

118+
def __init__(self, *args, **kwargs):
119+
super().__init__(*args, **kwargs)
120+
self.tracer = trace.get_tracer(__name__)
121+
110122
@task(1)
111123
def index(self):
112-
self.client.get("/")
124+
with self.tracer.start_as_current_span("user_index"):
125+
logging.info("User accessing index page")
126+
self.client.get("/")
113127

114128
@task(10)
115129
def browse_product(self):
116-
self.client.get("/api/products/" + random.choice(products))
130+
product = random.choice(products)
131+
with self.tracer.start_as_current_span("user_browse_product", attributes={"product.id": product}):
132+
logging.info(f"User browsing product: {product}")
133+
self.client.get("/api/products/" + product)
117134

118135
@task(3)
119136
def get_recommendations(self):
120-
params = {
121-
"productIds": [random.choice(products)],
122-
}
123-
self.client.get("/api/recommendations", params=params)
137+
product = random.choice(products)
138+
with self.tracer.start_as_current_span("user_get_recommendations", attributes={"product.id": product}):
139+
logging.info(f"User getting recommendations for product: {product}")
140+
params = {
141+
"productIds": [product],
142+
}
143+
self.client.get("/api/recommendations", params=params)
124144

125145
@task(3)
126146
def get_ads(self):
127-
params = {
128-
"contextKeys": [random.choice(categories)],
129-
}
130-
self.client.get("/api/data/", params=params)
147+
category = random.choice(categories)
148+
with self.tracer.start_as_current_span("user_get_ads", attributes={"category": str(category)}):
149+
logging.info(f"User getting ads for category: {category}")
150+
params = {
151+
"contextKeys": [category],
152+
}
153+
self.client.get("/api/data/", params=params)
131154

132155
@task(3)
133156
def view_cart(self):
134-
self.client.get("/api/cart")
157+
with self.tracer.start_as_current_span("user_view_cart"):
158+
logging.info("User viewing cart")
159+
self.client.get("/api/cart")
135160

136161
@task(2)
137162
def add_to_cart(self, user=""):
138163
if user == "":
139164
user = str(uuid.uuid1())
140165
product = random.choice(products)
141-
self.client.get("/api/products/" + product)
142-
cart_item = {
143-
"item": {
144-
"productId": product,
145-
"quantity": random.choice([1, 2, 3, 4, 5, 10]),
146-
},
147-
"userId": user,
148-
}
149-
self.client.post("/api/cart", json=cart_item)
166+
quantity = random.choice([1, 2, 3, 4, 5, 10])
167+
with self.tracer.start_as_current_span("user_add_to_cart",
168+
attributes={"user.id": user, "product.id": product, "quantity": quantity}):
169+
logging.info(f"User {user} adding {quantity} of product {product} to cart")
170+
self.client.get("/api/products/" + product)
171+
cart_item = {
172+
"item": {
173+
"productId": product,
174+
"quantity": quantity,
175+
},
176+
"userId": user,
177+
}
178+
self.client.post("/api/cart", json=cart_item)
150179

151180
@task(1)
152181
def checkout(self):
153182
# checkout call with an item added to cart
154183
user = str(uuid.uuid1())
155-
self.add_to_cart(user=user)
156-
checkout_person = random.choice(people)
157-
checkout_person["userId"] = user
158-
self.client.post("/api/checkout", json=checkout_person)
184+
with self.tracer.start_as_current_span("user_checkout_single", attributes={"user.id": user}):
185+
self.add_to_cart(user=user)
186+
checkout_person = random.choice(people)
187+
checkout_person["userId"] = user
188+
self.client.post("/api/checkout", json=checkout_person)
189+
logging.info(f"Checkout completed for user {user}")
159190

160191
@task(1)
161192
def checkout_multi(self):
162193
# checkout call which adds 2-4 different items to cart before checkout
163194
user = str(uuid.uuid1())
164-
for i in range(random.choice([2, 3, 4])):
165-
self.add_to_cart(user=user)
166-
checkout_person = random.choice(people)
167-
checkout_person["userId"] = user
168-
self.client.post("/api/checkout", json=checkout_person)
195+
item_count = random.choice([2, 3, 4])
196+
with self.tracer.start_as_current_span("user_checkout_multi",
197+
attributes={"user.id": user, "item.count": item_count}):
198+
for i in range(item_count):
199+
self.add_to_cart(user=user)
200+
checkout_person = random.choice(people)
201+
checkout_person["userId"] = user
202+
self.client.post("/api/checkout", json=checkout_person)
203+
logging.info(f"Multi-item checkout completed for user {user}")
169204

170205
@task(5)
171206
def flood_home(self):
172-
for _ in range(0, get_flagd_value("loadGeneratorFloodHomepage")):
173-
self.client.get("/")
207+
flood_count = get_flagd_value("loadGeneratorFloodHomepage")
208+
if flood_count > 0:
209+
with self.tracer.start_as_current_span("user_flood_home", attributes={"flood.count": flood_count}):
210+
logging.info(f"User flooding homepage {flood_count} times")
211+
for _ in range(0, flood_count):
212+
self.client.get("/")
174213

175214
def on_start(self):
176-
ctx = baggage.set_baggage("session.id", str(uuid.uuid4()))
177-
ctx = baggage.set_baggage("synthetic_request", "true", context=ctx)
178-
context.attach(ctx)
179-
self.index()
215+
with self.tracer.start_as_current_span("user_session_start"):
216+
session_id = str(uuid.uuid4())
217+
logging.info(f"Starting user session: {session_id}")
218+
ctx = baggage.set_baggage("session.id", session_id)
219+
ctx = baggage.set_baggage("synthetic_request", "true", context=ctx)
220+
context.attach(ctx)
221+
self.index()
180222

181223

182224
browser_traffic_enabled = os.environ.get("LOCUST_BROWSER_TRAFFIC_ENABLED", "").lower() in ("true", "yes", "on")
@@ -185,30 +227,38 @@ def on_start(self):
185227
class WebsiteBrowserUser(PlaywrightUser):
186228
headless = True # to use a headless browser, without a GUI
187229

230+
def __init__(self, *args, **kwargs):
231+
super().__init__(*args, **kwargs)
232+
self.tracer = trace.get_tracer(__name__)
233+
188234
@task
189235
@pw
190236
async def open_cart_page_and_change_currency(self, page: PageWithRetry):
191-
try:
192-
page.on("console", lambda msg: print(msg.text))
193-
await page.route('**/*', add_baggage_header)
194-
await page.goto("/cart", wait_until="domcontentloaded")
195-
await page.select_option('[name="currency_code"]', 'CHF')
196-
await page.wait_for_timeout(2000) # giving the browser time to export the traces
197-
except:
198-
pass
237+
with self.tracer.start_as_current_span("browser_change_currency"):
238+
try:
239+
page.on("console", lambda msg: print(msg.text))
240+
await page.route('**/*', add_baggage_header)
241+
await page.goto("/cart", wait_until="domcontentloaded")
242+
await page.select_option('[name="currency_code"]', 'CHF')
243+
await page.wait_for_timeout(2000) # giving the browser time to export the traces
244+
logging.info("Currency changed to CHF")
245+
except Exception as e:
246+
logging.error(f"Error in change currency task: {str(e)}")
199247

200248
@task
201249
@pw
202250
async def add_product_to_cart(self, page: PageWithRetry):
203-
try:
204-
page.on("console", lambda msg: print(msg.text))
205-
await page.route('**/*', add_baggage_header)
206-
await page.goto("/", wait_until="domcontentloaded")
207-
await page.click('p:has-text("Roof Binoculars")', wait_until="domcontentloaded")
208-
await page.click('button:has-text("Add To Cart")', wait_until="domcontentloaded")
209-
await page.wait_for_timeout(2000) # giving the browser time to export the traces
210-
except:
211-
pass
251+
with self.tracer.start_as_current_span("browser_add_to_cart"):
252+
try:
253+
page.on("console", lambda msg: print(msg.text))
254+
await page.route('**/*', add_baggage_header)
255+
await page.goto("/", wait_until="domcontentloaded")
256+
await page.click('p:has-text("Roof Binoculars")', wait_until="domcontentloaded")
257+
await page.click('button:has-text("Add To Cart")', wait_until="domcontentloaded")
258+
await page.wait_for_timeout(2000) # giving the browser time to export the traces
259+
logging.info("Product added to cart successfully")
260+
except Exception as e:
261+
logging.error(f"Error in add to cart task: {str(e)}")
212262

213263

214264
async def add_baggage_header(route: Route, request: Request):

src/load-generator/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ opentelemetry-instrumentation-system-metrics==0.55b1
1111
opentelemetry-instrumentation-urllib3==0.55b1
1212
opentelemetry-sdk==1.34.1
1313
opentelemetry-semantic-conventions==0.55b1
14+
opentelemetry-instrumentation-logging==0.55b1

0 commit comments

Comments
 (0)