SlideShare a Scribd company logo
Why you should be
using structured logs
Stefan Krawczyk
@stefkrawczyk
linkedin.com/in/skrawczyk
Try out Stitch Fix → goo.gl/Q3tCQ3
Outline:
What is Stitch Fix/Who am I?
What logs am I talking about?
What are structured logs?
Why are structured logs more fun?
Caveats with structured logs
How to implement a structured logger
Conclusion
Outline:
> What is Stitch Fix/Who am I?
What logs am I talking about?
What are structured logs?
Why are structured logs more fun?
Caveats with structured logs
How to implement a structured logger
Conclusion
What
•
Personal Styling Service
Stitch Fix: Personal Styling Service
•
•
•
•
•
Stitch Fix: Algorithms Org.
•
•
•
•
•
•
•
•
•
•
Stitch Fix: Algorithms Org.
•
•
•
•
• < - - - - - - - - - - - - - - - - Manager of Algo. Dev. Platform Team.
•
•
•
•
•
Outline:
What is Stitch Fix/Who am I?
> What logs am I talking about?
What are structured logs?
Why are structured logs more fun?
Caveats with structured logs
How to implement a structured logger
Conclusion
What logs am I talking about?
Aug 13 14:08:58 ip-10-8-0-122 com.apple.xpc.launchd[1] (com.google.keystone.daemon[20144]): Endpoint has been activated through legac
launch(3) APIs. Please switch to XPC or bootstrap_check_in(): com.google.Keystone.Daemon.Administration
Aug 13 14:08:58 ip-10-8-0-122 com.apple.xpc.launchd[1] (com.google.keystone.daemon[20144]): Endpoint has been activated through legac
launch(3) APIs. Please switch to XPC or bootstrap_check_in(): com.google.Keystone.Daemon.UpdateEngine
Aug 13 14:11:30 ip-10-8-0-122 com.apple.xpc.launchd[1] (com.jamfsoftware.task.Every 15 Minutes[20014]): Service exited with abnormal
code: 1
Aug 13 14:19:47 ip-10-8-0-122 syslogd[64]: ASL Sender Statistics
Aug 13 14:20:45 ip-10-8-0-122 AOUDownloadCount[20978]: ERROR|AOUDownloadCount.m|376L|Error:AOUDownloadCount::sendDownloadCountInfo:ge
DownloadCountInfo failed.
Aug 13 14:29:26 ip-10-8-0-122 com.apple.xpc.launchd[1] (com.jamfsoftware.task.Every 15 Minutes[21148]): Service exited with abnormal
code: 1
Aug 13 14:30:04 ip-10-8-0-122 syslogd[64]: ASL Sender Statistics
Aug 13 14:30:58 ip-10-8-0-122 com.apple.xpc.launchd[1] (com.apple.quicklook[21738]): Endpoint has been activated through legacy
launch(3) APIs. Please switch to XPC or bootstrap_check_in(): com.apple.quicklook
Aug 13 14:35:23 ip-10-8-0-122 com.apple.xpc.launchd[1] (com.apple.imfoundation.IMRemoteURLConnectionAgent): Unknown key for integer:
_DirtyJetsamMemoryLimit
Aug 13 14:42:27 ip-10-8-0-122 syslogd[64]: ASL Sender Statistics
Aug 13 14:46:31 ip-10-8-0-122 com.apple.xpc.launchd[1] (com.jamfsoftware.task.Every 15 Minutes[24285]): Service exited with abnormal
code: 1
Aug 13 14:47:20 ip-10-8-0-122 com.apple.xpc.launchd[1] (com.apple.xpc.launchd.domain.user.502): Service
"com.apple.xpc.launchd.unmanaged.loginwindow.123" tried to register for endpoint "com.apple.tsm.uiserver" already registered by owner
com.apple.SystemUIServer.agent
What logs am I talking about?
●
○
●
●
○
import logging
logger = logging.getLogger(__name__)
...
logger.info('Computing %s for client_id: %s, shipment_id: %s', item_id, client_id, shipment_id)
Example use cases for logs
●
●
●
●
●
●
Examples on how you likely consume logs
●
○ grep “client”
●
○ egrep “client[_id: ]+(d+)”
●
○
Question:
Who here likes using logs?
Outline:
What is Stitch Fix/Who am I?
What logs am I talking about?
> What are structured logs?
Why are structured logs more fun?
Caveats with structured logs
How to implement a structured logger
Conclusion
What are structured logs?
2019-07-10 18:46:34,501 INFO logging_talk_code __main__ 18 Computing 10001 for client_id 1 shipment_id
2
{
"message": "Computing 10001 for client_id 1 shipment_id 2",
"payload": {
"client_id": 1,
"shipment_id": 2,
"item_id": 10001
},
"metadata": {
"code": {
"file_url": "/a/url/code.py",
"line_number": 186,
"file_name": "code.py",
What are structured logs?
logger.info('Computing %s for client_id: %s, shipment_id: %s', item_id, client_id, shipment_id)
logger.info(f'Computing {item_id} for client_id: {client_id}, shipment_id: {shipment_id}')
logger.info('Computing %(item_id)s for client_id: %(client_id)s shipment_id: %(shipment_id)s',
{'client_id': client_id, 'item_id': item_id, 'shipment_id': shipment_id})
What are structured logs?
logger.info('Computing %s for client_id: %s, shipment_id: %s', item_id, client_id, shipment_id)
{
"message": "Computing 10001 for client_id 1
shipment_id 2",
"payload": {
"client_id": 1,
"shipment_id": 2,
"item_id": 10001
},
"metadata": {
"code": {
"file_url": "/a/url/path/code.py",
"line_number": 186,
Computing 10001 for client_id 1 shipment_id
What are structured logs?
-
-
-
-
-
-
What are structured logs?
-
-
-
-
-
-
●
●
●
●
What are structured logs @ Stitch Fix?
logger.info('Some message optional %(interpolation)s', log_payload)
●
●
●
●
●
●
●
●
What are structured logs @ Stitch Fix?
●
●
●
●
●
●
●
●
https://commons.wikimedia.org/wiki/File:Python_batteries_included.jpg
What are structured logs @ Stitch Fix?
What are structured logs @ Stitch Fix?
Structured logs @ Stitch Fix: Example Usage
Launched an experiment
Outline:
What is Stitch Fix/Who am I?
What logs am I talking about?
What are structured logs?
> Why are structured logs more fun?
Caveats with structured logs
How to implement a structured logger
Conclusion
Why are structured logs more fun?
●
●
●
{
"message": "Computing 10001 for client_id 1
shipment_id 2",
"payload": {
"client_id": 1,
"shipment_id": 2,
"item_id": 10001
},
"metadata": {
"code": {
"file_url": "/a/url/code.py",
"line_number": 186,
"file_name": "code.py",
"module": "code",
"function_name": "up_next"
},
"instance": {
"ami_id": "XXXX",
"id": "XXXX",
"type": "c4.2xlarge",
"tags": {
"aws:autoscaling:groupName":
Why are structured logs more fun?
Why are structured logs more fun?
Slow, Tedious; Not Fun
Streamlined, fast; Fun!
Why are structured logs more fun?
○
-
Why are structured logs more fun?
● Developer knowledge transfer
○ much easier to pick up, use, and evangelize.
Leverage: Developer Knowledge Transfer
● one incentivized way to write a logline!
● only one format that logs come out in!
●
logger.info('some text %(var1)s, %(var2)s', {'var1': value1, 'var2': value2})
Why are structured logs more fun?
●
○
● Oncall debugging/monitoring:
○ inspecting & using logs is easier since it's in a structured format (e.g. stacktraces)
Leverage: Oncall & Debugging/Monitoring
Leverage: Oncall & Debugging/Monitoring
Leverage: Oncall & Debugging/Monitoring
●
Leverage: Oncall & Debugging/Monitoring
payload.warehouse payload.row payload.shipment_idpayload.client_id
Leverage: Oncall & Debugging/Monitoring
secrets
Why are structured logs more fun?
●
○
●
○
● Easy to ingest/use in multiple places/pieces of infrastructure
○ e.g. elasticsearch, presto, ETL, custom reports
Leverage: Easy to reuse in multiple places
●
●
with m_app_traces AS (
SELECT
json_extract_scalar(m.payload, '$.metadata.http_headers["stitchfix-request-id"]') as request_id,
json_extract_scalar(m.payload, '$.payload.data.functionality') as m_functionality,
json_extract_scalar(m.payload, '$.payload.data.context.client_id') as client_id,
json_extract_scalar(m.payload, '$.payload.data.context.shipment_id') as shipment_id,
json_extract_scalar(m.payload, '$.payload.data.context.stylist_id') as stylist_id
FROM
kafkatopic.secret_app_logs m
WHERE
m.date = '20190817'
),
...
Leverage: Easy to reuse in multiple places
def create_df_for_analysis(lines: List[str]) -> int:
"""Counts number of errors in log lines"""
df_base = {'level': [], 'message': [], 'client_id': [], 'timestamp': []}
for line in lines:
json_dict = json.loads(line) # We know the structure so easy to analyze
df_base['timestamp'] = json_dict['timestamp']
df_base['level'] = json_dict['level']
df_base['message'] = json_dict['message']
df_base['client_id'] = json_dict['payload'].get('client_id')
df = pd.DataFrame(df_base)
df.index = df['timestamp']
return df
Why are structured logs more fun?
●
○
●
○
●
○
● Can tie other systems together more easily:
○ e.g. nginx, web apps, ETLs, etc.; can use/join on the same “key” names.
Leverage: tie systems together
●
with app1 AS (
SELECT
m.event_timestamp,
json_extract_scalar(m.payload, '$.query_params.client_id') as client_id,
json_extract_scalar(m.payload, '$.response_time') as response_time
FROM
kafkatopic.nginx_logs m
WHERE
m.date = '20190817'
),
app2 AS (
SELECT
m.event_timestamp,
json_extract_scalar(m.payload, '$.payload.client_id') as client_id,
json_extract_scalar(m.payload, '$.payload.score') as score
FROM
kafkatopic.app1_logs m
WHERE
m.date = '20190817'
)
SELECT
a1.client_id, a1.event_timestamp,
a1.response_time,
a2.score
FROM
app1 a1
JOIN
app2 a2
ON
a1.client_id = a2.client_id
Outline:
What is Stitch Fix/Who am I?
What logs am I talking about?
What are structured logs?
Why are structured logs more fun?
> Caveats with structured logs
How to implement a structured logger
Conclusion
Untyped -> Typed
○
logger.info('Computing scores for client_id: %(client_id)s shipment_id: %(shipment_id)s',
{'client_id': client_id, 'shipment_id': shipment_id})
Untyped -> Typed
○
●
○
●
○
logger.info('Computing scores for client_id: %(client_id)s shipment_id: %(shipment_id)s',
{'client_id': client_id, 'shipment_id': shipment_id})
Naming
●
logger.info('Computing scores for client_id: %(client_id)s shipment_id: %(shipment_id)s',
{'client_id': client_id, 'shipment_id': shipment_id})
Naming
●
○
○
■
logger.info('Computing scores for client_id: %(client_id)s shipment_id: %(shipment_id)s',
{'client_id': client_id, 'shipment_id': shipment_id})
Serialization
●
○
●
●
Serialization
●
○
●
●
●
●
Human Readable Logs
https://docs.python.org/3/howto/logging-cookbook.html#logging-to-multiple-destinations
Computing scores for client_id 1 shipment_id 2
"metadata": {
"code": {
"file_url": "/a/url/path/code.py",
"line_number": 186,
"file_name": "code.py",
"module": "code",
"function_name": "up_next"
},
"instance": {
"ami_id": "XXXX",
"id": "XXXX",
"type": "c4.2xlarge",
"tags": {
"aws:autoscaling:groupName": "k-prod",
"stack": "flotilla",
"stitchfix:clusterName": "k-prod",
"clusterName": "k-prod",
"stitchfix:applicationName": "our_app",
"name": "k-prod"
}
},
Human Readable Logs
https://docs.python.org/3/howto/logging-cookbook.html#logging-to-multiple-destinations
Computing scores for client_id 1 shipment_id 2
"metadata": {
"code": {
"file_url": "/a/url/path/code.py",
"line_number": 186,
"file_name": "code.py",
"module": "code",
"function_name": "up_next"
},
"instance": {
"ami_id": "XXXX",
"id": "XXXX",
"type": "c4.2xlarge",
"tags": {
"aws:autoscaling:groupName": "k-prod",
"stack": "flotilla",
"stitchfix:clusterName": "k-prod",
"clusterName": "k-prod",
"stitchfix:applicationName": "our_app",
"name": "k-prod"
}
},
●
●
○
○
Outline:
What is Stitch Fix/Who am I?
What logs am I talking about?
What are structured logs?
Why are structured logs more fun?
Caveats with structured logs
> How to implement a structured logger
Conclusion
First: Python Logging Module
Python
Logging
Module 3.6+
●
●
●
https://docs.python.org/3/howto/logging.html
https://docs.python.org/3/howto/logging-cookbook.html
Python
Logging
Module 3.6+
●
●
●
Python
Logging
Module 3.6+
●
Python
Logging
Module 3.6+
●
●
●
Python
Logging
Module 3.6+
●
TL;DR: Logging module (3.6+)
• LogRecord
• Formatter
•
• Handler
•
•
• Logger
•
•
https://docs.python.org/3/howto/logging.html
https://docs.python.org/3/howto/logging-cookbook.html
Second: Options for implementation
Very happy we're in Python
•
def info(self, msg, *args, **kwargs):
•
•
•
•
Options
●
Options
●
●
○
■
JSON structured logger
●
○
■ format()
●
○
■ format()/emit()
○
■ makeRecord()
■ format()
Example code
Code
class PyBayFormatter(logging.Formatter):
"""Implementation of JSON structured logging that works for most handlers."""
def format(self, record: logging.LogRecord) -> str:
"""Overrides parent format function.
:param record: logging.LogRecord object
:return: JSON string
"""
payload = self.make_structured_dict(record)
if hasattr(record, 'exc_info') and record.exc_info: # adds stack traces!
payload['stack_trace'] = self.formatException(record.exc_info)
return json.dumps(payload)
@staticmethod
def make_structured_dict(record: logging.LogRecord) -> dict:
"""Creates dictionary that we want to JSON-ify.
:param record: the LogRecord object.
:return: dict
"""
# next slide
Code
@staticmethod
def make_structured_dict(record: logging.LogRecord) -> dict:
"""Creates dictionary that we want to JSON-ify.
:param record: the LogRecord object.
:return: dict
"""
message_str = record.getMessage() # this interpolates '%' markers in the message.
if isinstance(record.args, dict): # handles case for backwards compatibility of people passing multiple args.
user_payload = record.args
else:
user_payload = {'value': repr(record.args)}
return {
'payload': user_payload,
'message': message_str,
'version': '0.0.1', # always good to have a schema version field
'meta': {
'level': record.levelname,
'name': record.name,
'pid': record.process,
'code': {
'file_name': record.filename,
'file_url': record.pathname,
# etc ...
},
}
}
Hooking this into logger config
[formatters]
keys=PyBayFormatter
[formatter_PyBayFormatter]
class=package.PyBayFormatter
[handlers]
keys=console_handler
[handler_console_handler]
class=StreamHandler
formatter=PyBayFormatter
args=(sys.stdout,)
[loggers]
keys=root
[logger_root]
level=INFO
handlers=console_handler
import logging
import logging.config
import sys
config_path = 'some_dir/config.ini'
logging.config.fileConfig(config_path)
logger = logging.getLogger(__name__)
logger.info('Winning', {'key1': 1, 'key2': 'a'})
### ------- OR manually ----------------
logger = logging.getLogger(__name__)
sh = logging.StreamHandler(sys.stdout)
sh.setFormatter(PyBayFormatter())
logger.addHandler(sh)
logger.setLevel(logging.INFO)
logger.info('Hi this is a message', {'and': 'some', 'structured': 'logs'})
Hooking this into logger config
[formatters]
keys=PyBayFormatter
[formatter_PyBayFormatter]
class=package.PyBayFormatter
[handlers]
keys=console_handler
[handler_console_handler]
class=StreamHandler
formatter =PyBayFormatter
args=(sys.stdout,)
[loggers]
keys=root
[logger_root]
level=INFO
handlers =console_handler
import logging
import logging.config
import sys
config_path = 'some_dir/config.ini'
logging.config.fileConfig(config_path)
logger = logging.getLogger(__name__)
logger.info('Winning', {'key1': 1, 'key2': 'a'})
### ------- OR manually ----------------
logger = logging.getLogger(__name__)
sh = logging.StreamHandler(sys.stdout)
sh.setFormatter(PyBayFormatter())
logger.addHandler(sh)
logger.setLevel(logging.INFO)
logger.info('Hi this is a message', {'and': 'some', 'structured': 'logs'})
Hooking this into logger config
[formatters]
keys=PyBayFormatter
[formatter_PyBayFormatter]
class=package.PyBayFormatter
[handlers]
keys=console_handler
[handler_console_handler]
class=StreamHandler
formatter =PyBayFormatter
args=(sys.stdout,)
[loggers]
keys=root
[logger_root]
level=INFO
handlers =console_handler
import logging
import logging.config
import sys
config_path = 'some_dir/config.ini'
logging.config.fileConfig(config_path)
logger = logging.getLogger(__name__)
logger.info('Winning', {'key1': 1, 'key2': 'a'})
### ------- OR manually ----------------
logger = logging.getLogger(__name__)
sh = logging.StreamHandler(sys.stdout)
sh.setFormatter(PyBayFormatter())
logger.addHandler(sh)
logger.setLevel(logging.INFO)
logger.info('Hi this is a message', {'and': 'some', 'structured': 'logs'})
You now just need to push the logs...
→ ✅
→
-
-
-
-
-
-
Outline:
What is Stitch Fix/Who am I?
What logs am I talking about?
What are structured logs?
Why are structured logs more fun?
Caveats with structured logs
How to implement a structured logger
> Conclusion
Conclusion
•
logger.info('Computing %(item_id)s for client_id: %(client_id)s shipment_id: %(shipment_id)s',
{'client_id': client_id, 'item_id': item_id, 'shipment_id': shipment_id})
{
"message": "Computing 10001 for client_id 1 shipment_id 2",
"payload": {
"client_id": 1,
"shipment_id": 2,
"item_id": 10001
},
"metadata": {
"code": {
"file_url": "/a/url/code.py",
"line_number": 186,
"file_name": "code.py",
"module": "code",
"function_name": "up_next"
},
"instance": {
Conclusion
•
•
●
●
●
Conclusion
•
•
•
●
●
●
●
Conclusion
•
•
•
•
Conclusion
•
•
•
•
•
https://docs.python.org/3/howto/logging.html
https://docs.python.org/3/howto/logging-cookbook.html
Thank you! We’re hiring! Questions?
Try out Stitch Fix → goo.gl/Q3tCQ3@stefkrawczyk
linkedin.com/in/skrawczyk

More Related Content

PDF
Enabling Data Scientists to easily create and own Kafka Consumers
PDF
Big Data Day LA 2016/ Hadoop/ Spark/ Kafka track - Data Provenance Support in...
PPTX
More Data, More Problems: Evolving big data machine learning pipelines with S...
PPTX
Big Data Day LA 2016/ Hadoop/ Spark/ Kafka track - Iterative Spark Developmen...
PDF
Chapman: Building a High-Performance Distributed Task Service with MongoDB
PDF
Indexing and Performance Tuning
PDF
The Mechanics of Testing Large Data Pipelines (QCon London 2016)
PDF
Benchx: An XQuery benchmarking web application
Enabling Data Scientists to easily create and own Kafka Consumers
Big Data Day LA 2016/ Hadoop/ Spark/ Kafka track - Data Provenance Support in...
More Data, More Problems: Evolving big data machine learning pipelines with S...
Big Data Day LA 2016/ Hadoop/ Spark/ Kafka track - Iterative Spark Developmen...
Chapman: Building a High-Performance Distributed Task Service with MongoDB
Indexing and Performance Tuning
The Mechanics of Testing Large Data Pipelines (QCon London 2016)
Benchx: An XQuery benchmarking web application

What's hot (20)

PDF
Hadoop meetup : HUGFR Construire le cluster le plus rapide pour l'analyse des...
PDF
Building data flows with Celery and SQLAlchemy
PDF
Joining the Club: Using Spark to Accelerate Big Data at Dollar Shave Club
PPTX
Engineering a robust(ish) data pipeline with Luigi and AWS Elastic Map Reduce
PDF
Integrate Solr with real-time stream processing applications
PDF
Scaling up data science applications
PDF
Processing large-scale graphs with Google(TM) Pregel
PPT
Distributed Queries in IDS: New features.
PDF
Monitoring Your ISP Using InfluxDB Cloud and Raspberry Pi
PDF
Presto in Treasure Data
PPTX
Apache Flink Training: DataStream API Part 2 Advanced
PPTX
APOC Pearls - Whirlwind Tour Through the Neo4j APOC Procedures Library
PDF
CMUデータベース輪読会第8回
PPTX
Sharding in MongoDB 4.2 #what_is_new
PDF
Scalding - the not-so-basics @ ScalaDays 2014
PDF
Engineering Fast Indexes for Big-Data Applications: Spark Summit East talk by...
PDF
Devtools cheatsheet
PDF
Scalable XQuery Processing with Zorba on top of MongoDB
PPTX
Using Cerberus and PySpark to validate semi-structured datasets
PPTX
New Indexing and Aggregation Pipeline Capabilities in MongoDB 4.2
Hadoop meetup : HUGFR Construire le cluster le plus rapide pour l'analyse des...
Building data flows with Celery and SQLAlchemy
Joining the Club: Using Spark to Accelerate Big Data at Dollar Shave Club
Engineering a robust(ish) data pipeline with Luigi and AWS Elastic Map Reduce
Integrate Solr with real-time stream processing applications
Scaling up data science applications
Processing large-scale graphs with Google(TM) Pregel
Distributed Queries in IDS: New features.
Monitoring Your ISP Using InfluxDB Cloud and Raspberry Pi
Presto in Treasure Data
Apache Flink Training: DataStream API Part 2 Advanced
APOC Pearls - Whirlwind Tour Through the Neo4j APOC Procedures Library
CMUデータベース輪読会第8回
Sharding in MongoDB 4.2 #what_is_new
Scalding - the not-so-basics @ ScalaDays 2014
Engineering Fast Indexes for Big-Data Applications: Spark Summit East talk by...
Devtools cheatsheet
Scalable XQuery Processing with Zorba on top of MongoDB
Using Cerberus and PySpark to validate semi-structured datasets
New Indexing and Aggregation Pipeline Capabilities in MongoDB 4.2
Ad

Similar to Why you should be using structured logs (20)

PDF
Hadoop, hive和scribe在运维方面的应用
PPTX
Build a DataWarehouse for your logs with Python, AWS Athena and Glue
KEY
Message:Passing - lpw 2012
PDF
Getting the best performance with PySpark - Spark Summit West 2016
PDF
Logging and ranting / Vytis Valentinavičius (Lamoda)
ODP
Get the most out of your security logs using syslog-ng
PDF
Getting The Best Performance With PySpark
PDF
Analyzing Log Data With Apache Spark
PDF
Log stage zero-cost structured logging
PDF
Logstage - zero-cost-tructured-logging
PDF
Improving PySpark Performance - Spark Beyond the JVM @ PyData DC 2016
ODT
Log4 C Developers Guide
PDF
Why Python 3
PDF
Introduction to python
ODP
Log Management Systems
PDF
SQLGitHub - Access GitHub API with SQL-like syntaxes
PDF
PDF
Spark DataFrames for Data Munging
KEY
London devops logging
PDF
How to build observability into Serverless (O'Reilly Velocity 2018)
Hadoop, hive和scribe在运维方面的应用
Build a DataWarehouse for your logs with Python, AWS Athena and Glue
Message:Passing - lpw 2012
Getting the best performance with PySpark - Spark Summit West 2016
Logging and ranting / Vytis Valentinavičius (Lamoda)
Get the most out of your security logs using syslog-ng
Getting The Best Performance With PySpark
Analyzing Log Data With Apache Spark
Log stage zero-cost structured logging
Logstage - zero-cost-tructured-logging
Improving PySpark Performance - Spark Beyond the JVM @ PyData DC 2016
Log4 C Developers Guide
Why Python 3
Introduction to python
Log Management Systems
SQLGitHub - Access GitHub API with SQL-like syntaxes
Spark DataFrames for Data Munging
London devops logging
How to build observability into Serverless (O'Reilly Velocity 2018)
Ad

Recently uploaded (20)

PPTX
Agentic AI Use Case- Contract Lifecycle Management (CLM).pptx
PDF
Addressing The Cult of Project Management Tools-Why Disconnected Work is Hold...
PDF
medical staffing services at VALiNTRY
PDF
Adobe Illustrator 28.6 Crack My Vision of Vector Design
PDF
AI in Product Development-omnex systems
PPTX
Introduction to Artificial Intelligence
PPTX
Oracle E-Business Suite: A Comprehensive Guide for Modern Enterprises
PDF
2025 Textile ERP Trends: SAP, Odoo & Oracle
PDF
T3DD25 TYPO3 Content Blocks - Deep Dive by André Kraus
PPTX
Agentic AI : A Practical Guide. Undersating, Implementing and Scaling Autono...
PDF
How to Migrate SBCGlobal Email to Yahoo Easily
PDF
Why TechBuilder is the Future of Pickup and Delivery App Development (1).pdf
PPTX
Odoo POS Development Services by CandidRoot Solutions
PDF
Digital Strategies for Manufacturing Companies
PDF
Adobe Premiere Pro 2025 (v24.5.0.057) Crack free
PDF
Design an Analysis of Algorithms I-SECS-1021-03
PDF
Audit Checklist Design Aligning with ISO, IATF, and Industry Standards — Omne...
PDF
How Creative Agencies Leverage Project Management Software.pdf
PDF
Understanding Forklifts - TECH EHS Solution
PDF
Navsoft: AI-Powered Business Solutions & Custom Software Development
Agentic AI Use Case- Contract Lifecycle Management (CLM).pptx
Addressing The Cult of Project Management Tools-Why Disconnected Work is Hold...
medical staffing services at VALiNTRY
Adobe Illustrator 28.6 Crack My Vision of Vector Design
AI in Product Development-omnex systems
Introduction to Artificial Intelligence
Oracle E-Business Suite: A Comprehensive Guide for Modern Enterprises
2025 Textile ERP Trends: SAP, Odoo & Oracle
T3DD25 TYPO3 Content Blocks - Deep Dive by André Kraus
Agentic AI : A Practical Guide. Undersating, Implementing and Scaling Autono...
How to Migrate SBCGlobal Email to Yahoo Easily
Why TechBuilder is the Future of Pickup and Delivery App Development (1).pdf
Odoo POS Development Services by CandidRoot Solutions
Digital Strategies for Manufacturing Companies
Adobe Premiere Pro 2025 (v24.5.0.057) Crack free
Design an Analysis of Algorithms I-SECS-1021-03
Audit Checklist Design Aligning with ISO, IATF, and Industry Standards — Omne...
How Creative Agencies Leverage Project Management Software.pdf
Understanding Forklifts - TECH EHS Solution
Navsoft: AI-Powered Business Solutions & Custom Software Development

Why you should be using structured logs

  • 1. Why you should be using structured logs Stefan Krawczyk @stefkrawczyk linkedin.com/in/skrawczyk Try out Stitch Fix → goo.gl/Q3tCQ3
  • 2. Outline: What is Stitch Fix/Who am I? What logs am I talking about? What are structured logs? Why are structured logs more fun? Caveats with structured logs How to implement a structured logger Conclusion
  • 3. Outline: > What is Stitch Fix/Who am I? What logs am I talking about? What are structured logs? Why are structured logs more fun? Caveats with structured logs How to implement a structured logger Conclusion
  • 5. Stitch Fix: Personal Styling Service • • • • •
  • 6. Stitch Fix: Algorithms Org. • • • • • • • • • •
  • 7. Stitch Fix: Algorithms Org. • • • • • < - - - - - - - - - - - - - - - - Manager of Algo. Dev. Platform Team. • • • • •
  • 8. Outline: What is Stitch Fix/Who am I? > What logs am I talking about? What are structured logs? Why are structured logs more fun? Caveats with structured logs How to implement a structured logger Conclusion
  • 9. What logs am I talking about? Aug 13 14:08:58 ip-10-8-0-122 com.apple.xpc.launchd[1] (com.google.keystone.daemon[20144]): Endpoint has been activated through legac launch(3) APIs. Please switch to XPC or bootstrap_check_in(): com.google.Keystone.Daemon.Administration Aug 13 14:08:58 ip-10-8-0-122 com.apple.xpc.launchd[1] (com.google.keystone.daemon[20144]): Endpoint has been activated through legac launch(3) APIs. Please switch to XPC or bootstrap_check_in(): com.google.Keystone.Daemon.UpdateEngine Aug 13 14:11:30 ip-10-8-0-122 com.apple.xpc.launchd[1] (com.jamfsoftware.task.Every 15 Minutes[20014]): Service exited with abnormal code: 1 Aug 13 14:19:47 ip-10-8-0-122 syslogd[64]: ASL Sender Statistics Aug 13 14:20:45 ip-10-8-0-122 AOUDownloadCount[20978]: ERROR|AOUDownloadCount.m|376L|Error:AOUDownloadCount::sendDownloadCountInfo:ge DownloadCountInfo failed. Aug 13 14:29:26 ip-10-8-0-122 com.apple.xpc.launchd[1] (com.jamfsoftware.task.Every 15 Minutes[21148]): Service exited with abnormal code: 1 Aug 13 14:30:04 ip-10-8-0-122 syslogd[64]: ASL Sender Statistics Aug 13 14:30:58 ip-10-8-0-122 com.apple.xpc.launchd[1] (com.apple.quicklook[21738]): Endpoint has been activated through legacy launch(3) APIs. Please switch to XPC or bootstrap_check_in(): com.apple.quicklook Aug 13 14:35:23 ip-10-8-0-122 com.apple.xpc.launchd[1] (com.apple.imfoundation.IMRemoteURLConnectionAgent): Unknown key for integer: _DirtyJetsamMemoryLimit Aug 13 14:42:27 ip-10-8-0-122 syslogd[64]: ASL Sender Statistics Aug 13 14:46:31 ip-10-8-0-122 com.apple.xpc.launchd[1] (com.jamfsoftware.task.Every 15 Minutes[24285]): Service exited with abnormal code: 1 Aug 13 14:47:20 ip-10-8-0-122 com.apple.xpc.launchd[1] (com.apple.xpc.launchd.domain.user.502): Service "com.apple.xpc.launchd.unmanaged.loginwindow.123" tried to register for endpoint "com.apple.tsm.uiserver" already registered by owner com.apple.SystemUIServer.agent
  • 10. What logs am I talking about? ● ○ ● ● ○ import logging logger = logging.getLogger(__name__) ... logger.info('Computing %s for client_id: %s, shipment_id: %s', item_id, client_id, shipment_id)
  • 11. Example use cases for logs ● ● ● ● ● ●
  • 12. Examples on how you likely consume logs ● ○ grep “client” ● ○ egrep “client[_id: ]+(d+)” ● ○
  • 14. Outline: What is Stitch Fix/Who am I? What logs am I talking about? > What are structured logs? Why are structured logs more fun? Caveats with structured logs How to implement a structured logger Conclusion
  • 15. What are structured logs? 2019-07-10 18:46:34,501 INFO logging_talk_code __main__ 18 Computing 10001 for client_id 1 shipment_id 2 { "message": "Computing 10001 for client_id 1 shipment_id 2", "payload": { "client_id": 1, "shipment_id": 2, "item_id": 10001 }, "metadata": { "code": { "file_url": "/a/url/code.py", "line_number": 186, "file_name": "code.py",
  • 16. What are structured logs? logger.info('Computing %s for client_id: %s, shipment_id: %s', item_id, client_id, shipment_id) logger.info(f'Computing {item_id} for client_id: {client_id}, shipment_id: {shipment_id}') logger.info('Computing %(item_id)s for client_id: %(client_id)s shipment_id: %(shipment_id)s', {'client_id': client_id, 'item_id': item_id, 'shipment_id': shipment_id})
  • 17. What are structured logs? logger.info('Computing %s for client_id: %s, shipment_id: %s', item_id, client_id, shipment_id) { "message": "Computing 10001 for client_id 1 shipment_id 2", "payload": { "client_id": 1, "shipment_id": 2, "item_id": 10001 }, "metadata": { "code": { "file_url": "/a/url/path/code.py", "line_number": 186, Computing 10001 for client_id 1 shipment_id
  • 18. What are structured logs? - - - - - -
  • 19. What are structured logs? - - - - - -
  • 20. ● ● ● ● What are structured logs @ Stitch Fix? logger.info('Some message optional %(interpolation)s', log_payload)
  • 23. What are structured logs @ Stitch Fix?
  • 24. Structured logs @ Stitch Fix: Example Usage Launched an experiment
  • 25. Outline: What is Stitch Fix/Who am I? What logs am I talking about? What are structured logs? > Why are structured logs more fun? Caveats with structured logs How to implement a structured logger Conclusion
  • 26. Why are structured logs more fun? ● ● ● { "message": "Computing 10001 for client_id 1 shipment_id 2", "payload": { "client_id": 1, "shipment_id": 2, "item_id": 10001 }, "metadata": { "code": { "file_url": "/a/url/code.py", "line_number": 186, "file_name": "code.py", "module": "code", "function_name": "up_next" }, "instance": { "ami_id": "XXXX", "id": "XXXX", "type": "c4.2xlarge", "tags": { "aws:autoscaling:groupName":
  • 27. Why are structured logs more fun?
  • 28. Why are structured logs more fun? Slow, Tedious; Not Fun Streamlined, fast; Fun!
  • 29. Why are structured logs more fun? ○ -
  • 30. Why are structured logs more fun? ● Developer knowledge transfer ○ much easier to pick up, use, and evangelize.
  • 31. Leverage: Developer Knowledge Transfer ● one incentivized way to write a logline! ● only one format that logs come out in! ● logger.info('some text %(var1)s, %(var2)s', {'var1': value1, 'var2': value2})
  • 32. Why are structured logs more fun? ● ○ ● Oncall debugging/monitoring: ○ inspecting & using logs is easier since it's in a structured format (e.g. stacktraces)
  • 33. Leverage: Oncall & Debugging/Monitoring
  • 34. Leverage: Oncall & Debugging/Monitoring
  • 35. Leverage: Oncall & Debugging/Monitoring ●
  • 36. Leverage: Oncall & Debugging/Monitoring payload.warehouse payload.row payload.shipment_idpayload.client_id
  • 37. Leverage: Oncall & Debugging/Monitoring secrets
  • 38. Why are structured logs more fun? ● ○ ● ○ ● Easy to ingest/use in multiple places/pieces of infrastructure ○ e.g. elasticsearch, presto, ETL, custom reports
  • 39. Leverage: Easy to reuse in multiple places ● ● with m_app_traces AS ( SELECT json_extract_scalar(m.payload, '$.metadata.http_headers["stitchfix-request-id"]') as request_id, json_extract_scalar(m.payload, '$.payload.data.functionality') as m_functionality, json_extract_scalar(m.payload, '$.payload.data.context.client_id') as client_id, json_extract_scalar(m.payload, '$.payload.data.context.shipment_id') as shipment_id, json_extract_scalar(m.payload, '$.payload.data.context.stylist_id') as stylist_id FROM kafkatopic.secret_app_logs m WHERE m.date = '20190817' ), ...
  • 40. Leverage: Easy to reuse in multiple places def create_df_for_analysis(lines: List[str]) -> int: """Counts number of errors in log lines""" df_base = {'level': [], 'message': [], 'client_id': [], 'timestamp': []} for line in lines: json_dict = json.loads(line) # We know the structure so easy to analyze df_base['timestamp'] = json_dict['timestamp'] df_base['level'] = json_dict['level'] df_base['message'] = json_dict['message'] df_base['client_id'] = json_dict['payload'].get('client_id') df = pd.DataFrame(df_base) df.index = df['timestamp'] return df
  • 41. Why are structured logs more fun? ● ○ ● ○ ● ○ ● Can tie other systems together more easily: ○ e.g. nginx, web apps, ETLs, etc.; can use/join on the same “key” names.
  • 42. Leverage: tie systems together ● with app1 AS ( SELECT m.event_timestamp, json_extract_scalar(m.payload, '$.query_params.client_id') as client_id, json_extract_scalar(m.payload, '$.response_time') as response_time FROM kafkatopic.nginx_logs m WHERE m.date = '20190817' ), app2 AS ( SELECT m.event_timestamp, json_extract_scalar(m.payload, '$.payload.client_id') as client_id, json_extract_scalar(m.payload, '$.payload.score') as score FROM kafkatopic.app1_logs m WHERE m.date = '20190817' ) SELECT a1.client_id, a1.event_timestamp, a1.response_time, a2.score FROM app1 a1 JOIN app2 a2 ON a1.client_id = a2.client_id
  • 43. Outline: What is Stitch Fix/Who am I? What logs am I talking about? What are structured logs? Why are structured logs more fun? > Caveats with structured logs How to implement a structured logger Conclusion
  • 44. Untyped -> Typed ○ logger.info('Computing scores for client_id: %(client_id)s shipment_id: %(shipment_id)s', {'client_id': client_id, 'shipment_id': shipment_id})
  • 45. Untyped -> Typed ○ ● ○ ● ○ logger.info('Computing scores for client_id: %(client_id)s shipment_id: %(shipment_id)s', {'client_id': client_id, 'shipment_id': shipment_id})
  • 46. Naming ● logger.info('Computing scores for client_id: %(client_id)s shipment_id: %(shipment_id)s', {'client_id': client_id, 'shipment_id': shipment_id})
  • 47. Naming ● ○ ○ ■ logger.info('Computing scores for client_id: %(client_id)s shipment_id: %(shipment_id)s', {'client_id': client_id, 'shipment_id': shipment_id})
  • 50. Human Readable Logs https://docs.python.org/3/howto/logging-cookbook.html#logging-to-multiple-destinations Computing scores for client_id 1 shipment_id 2 "metadata": { "code": { "file_url": "/a/url/path/code.py", "line_number": 186, "file_name": "code.py", "module": "code", "function_name": "up_next" }, "instance": { "ami_id": "XXXX", "id": "XXXX", "type": "c4.2xlarge", "tags": { "aws:autoscaling:groupName": "k-prod", "stack": "flotilla", "stitchfix:clusterName": "k-prod", "clusterName": "k-prod", "stitchfix:applicationName": "our_app", "name": "k-prod" } },
  • 51. Human Readable Logs https://docs.python.org/3/howto/logging-cookbook.html#logging-to-multiple-destinations Computing scores for client_id 1 shipment_id 2 "metadata": { "code": { "file_url": "/a/url/path/code.py", "line_number": 186, "file_name": "code.py", "module": "code", "function_name": "up_next" }, "instance": { "ami_id": "XXXX", "id": "XXXX", "type": "c4.2xlarge", "tags": { "aws:autoscaling:groupName": "k-prod", "stack": "flotilla", "stitchfix:clusterName": "k-prod", "clusterName": "k-prod", "stitchfix:applicationName": "our_app", "name": "k-prod" } }, ● ● ○ ○
  • 52. Outline: What is Stitch Fix/Who am I? What logs am I talking about? What are structured logs? Why are structured logs more fun? Caveats with structured logs > How to implement a structured logger Conclusion
  • 59. TL;DR: Logging module (3.6+) • LogRecord • Formatter • • Handler • • • Logger • • https://docs.python.org/3/howto/logging.html https://docs.python.org/3/howto/logging-cookbook.html
  • 60. Second: Options for implementation
  • 61. Very happy we're in Python • def info(self, msg, *args, **kwargs): • • • •
  • 64. JSON structured logger ● ○ ■ format() ● ○ ■ format()/emit() ○ ■ makeRecord() ■ format()
  • 66. Code class PyBayFormatter(logging.Formatter): """Implementation of JSON structured logging that works for most handlers.""" def format(self, record: logging.LogRecord) -> str: """Overrides parent format function. :param record: logging.LogRecord object :return: JSON string """ payload = self.make_structured_dict(record) if hasattr(record, 'exc_info') and record.exc_info: # adds stack traces! payload['stack_trace'] = self.formatException(record.exc_info) return json.dumps(payload) @staticmethod def make_structured_dict(record: logging.LogRecord) -> dict: """Creates dictionary that we want to JSON-ify. :param record: the LogRecord object. :return: dict """ # next slide
  • 67. Code @staticmethod def make_structured_dict(record: logging.LogRecord) -> dict: """Creates dictionary that we want to JSON-ify. :param record: the LogRecord object. :return: dict """ message_str = record.getMessage() # this interpolates '%' markers in the message. if isinstance(record.args, dict): # handles case for backwards compatibility of people passing multiple args. user_payload = record.args else: user_payload = {'value': repr(record.args)} return { 'payload': user_payload, 'message': message_str, 'version': '0.0.1', # always good to have a schema version field 'meta': { 'level': record.levelname, 'name': record.name, 'pid': record.process, 'code': { 'file_name': record.filename, 'file_url': record.pathname, # etc ... }, } }
  • 68. Hooking this into logger config [formatters] keys=PyBayFormatter [formatter_PyBayFormatter] class=package.PyBayFormatter [handlers] keys=console_handler [handler_console_handler] class=StreamHandler formatter=PyBayFormatter args=(sys.stdout,) [loggers] keys=root [logger_root] level=INFO handlers=console_handler import logging import logging.config import sys config_path = 'some_dir/config.ini' logging.config.fileConfig(config_path) logger = logging.getLogger(__name__) logger.info('Winning', {'key1': 1, 'key2': 'a'}) ### ------- OR manually ---------------- logger = logging.getLogger(__name__) sh = logging.StreamHandler(sys.stdout) sh.setFormatter(PyBayFormatter()) logger.addHandler(sh) logger.setLevel(logging.INFO) logger.info('Hi this is a message', {'and': 'some', 'structured': 'logs'})
  • 69. Hooking this into logger config [formatters] keys=PyBayFormatter [formatter_PyBayFormatter] class=package.PyBayFormatter [handlers] keys=console_handler [handler_console_handler] class=StreamHandler formatter =PyBayFormatter args=(sys.stdout,) [loggers] keys=root [logger_root] level=INFO handlers =console_handler import logging import logging.config import sys config_path = 'some_dir/config.ini' logging.config.fileConfig(config_path) logger = logging.getLogger(__name__) logger.info('Winning', {'key1': 1, 'key2': 'a'}) ### ------- OR manually ---------------- logger = logging.getLogger(__name__) sh = logging.StreamHandler(sys.stdout) sh.setFormatter(PyBayFormatter()) logger.addHandler(sh) logger.setLevel(logging.INFO) logger.info('Hi this is a message', {'and': 'some', 'structured': 'logs'})
  • 70. Hooking this into logger config [formatters] keys=PyBayFormatter [formatter_PyBayFormatter] class=package.PyBayFormatter [handlers] keys=console_handler [handler_console_handler] class=StreamHandler formatter =PyBayFormatter args=(sys.stdout,) [loggers] keys=root [logger_root] level=INFO handlers =console_handler import logging import logging.config import sys config_path = 'some_dir/config.ini' logging.config.fileConfig(config_path) logger = logging.getLogger(__name__) logger.info('Winning', {'key1': 1, 'key2': 'a'}) ### ------- OR manually ---------------- logger = logging.getLogger(__name__) sh = logging.StreamHandler(sys.stdout) sh.setFormatter(PyBayFormatter()) logger.addHandler(sh) logger.setLevel(logging.INFO) logger.info('Hi this is a message', {'and': 'some', 'structured': 'logs'})
  • 71. You now just need to push the logs... → ✅ → - - - - - -
  • 72. Outline: What is Stitch Fix/Who am I? What logs am I talking about? What are structured logs? Why are structured logs more fun? Caveats with structured logs How to implement a structured logger > Conclusion
  • 73. Conclusion • logger.info('Computing %(item_id)s for client_id: %(client_id)s shipment_id: %(shipment_id)s', {'client_id': client_id, 'item_id': item_id, 'shipment_id': shipment_id}) { "message": "Computing 10001 for client_id 1 shipment_id 2", "payload": { "client_id": 1, "shipment_id": 2, "item_id": 10001 }, "metadata": { "code": { "file_url": "/a/url/code.py", "line_number": 186, "file_name": "code.py", "module": "code", "function_name": "up_next" }, "instance": {
  • 78. Thank you! We’re hiring! Questions? Try out Stitch Fix → goo.gl/Q3tCQ3@stefkrawczyk linkedin.com/in/skrawczyk