2525from opentelemetry .instrumentation .requests import RequestsInstrumentor
2626from opentelemetry .instrumentation .system_metrics import SystemMetricsInstrumentor
2727from opentelemetry .instrumentation .urllib3 import URLLib3Instrumentor
28+ from opentelemetry .instrumentation .logging import LoggingInstrumentor
2829from opentelemetry ._logs import set_logger_provider
2930from opentelemetry .exporter .otlp .proto .grpc ._log_exporter import (
3031 OTLPLogExporter ,
3940
4041from 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 ()
4750set_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
5157handler = 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
6572Jinja2Instrumentor ().instrument ()
6673RequestsInstrumentor ().instrument ()
6774SystemMetricsInstrumentor ().instrument ()
6875URLLib3Instrumentor ().instrument ()
69- logging .info ("Instrumentation complete" )
76+
77+ logging .info ("Instrumentation complete - logs will now include trace context" )
7078
7179# Initialize Flagd provider
7280base_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):
107115class 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
182224browser_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
214264async def add_baggage_header (route : Route , request : Request ):
0 commit comments