Skip to content

Commit 4d9ff29

Browse files
authored
Standardize Datadog log format (#46)
1 parent ae4d801 commit 4d9ff29

File tree

6 files changed

+299
-290
lines changed

6 files changed

+299
-290
lines changed

requirements-dev.txt

Lines changed: 138 additions & 108 deletions
Large diffs are not rendered by default.

requirements.txt

Lines changed: 105 additions & 118 deletions
Large diffs are not rendered by default.

scfw/configure.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,21 @@
1616

1717
_log = logging.getLogger(__name__)
1818

19+
DD_SOURCE = "scfw"
20+
"""
21+
Source value for Datadog logging.
22+
"""
23+
24+
DD_SERVICE = "scfw"
25+
"""
26+
Service value for Datadog logging.
27+
"""
28+
29+
DD_ENV = "dev"
30+
"""
31+
Default environment value for Datadog logging.
32+
"""
33+
1934
DD_API_KEY_VAR = "DD_API_KEY"
2035
"""
2136
The environment variable under which the firewall looks for a Datadog API key.
@@ -165,8 +180,8 @@ def _configure_agent_logging(port: str):
165180
"logs:\n"
166181
" - type: tcp\n"
167182
f" port: {port}\n"
168-
' service: "scfw"\n'
169-
' source: "scfw"\n'
183+
f' service: "{DD_SERVICE}"\n'
184+
f' source: "{DD_SOURCE}"\n'
170185
)
171186

172187
try:

scfw/loggers/dd_agent_logger.py

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,51 +2,19 @@
22
Configures a logger for sending firewall logs to a local Datadog Agent.
33
"""
44

5-
import json
65
import logging
76
import os
87
import socket
98

10-
import scfw
119
from scfw.configure import DD_AGENT_PORT_VAR
1210
from scfw.logger import FirewallLogger
13-
from scfw.loggers.dd_logger import DDLogger
11+
from scfw.loggers.dd_logger import DDLogFormatter, DDLogger
1412

1513
_log = logging.getLogger(__name__)
1614

1715
_DD_LOG_NAME = "dd_agent_log"
1816

1917

20-
class _DDLogFormatter(logging.Formatter):
21-
"""
22-
A custom JSON formatter for firewall logs.
23-
"""
24-
DD_ENV = "dev"
25-
DD_VERSION = scfw.__version__
26-
DD_SERVICE = DD_SOURCE = "scfw"
27-
28-
def format(self, record) -> str:
29-
"""
30-
Format a log record as a JSON string.
31-
32-
Args:
33-
record: The log record to be formatted.
34-
"""
35-
log_record = {
36-
"source": self.DD_SOURCE,
37-
"service": self.DD_SERVICE,
38-
"version": self.DD_VERSION
39-
}
40-
41-
env = os.getenv("DD_ENV")
42-
log_record["env"] = env if env else self.DD_ENV
43-
44-
for key in {"action", "created", "ecosystem", "msg", "targets"}:
45-
log_record[key] = record.__dict__[key]
46-
47-
return json.dumps(log_record) + '\n'
48-
49-
5018
class _DDLogHandler(logging.Handler):
5119
def emit(self, record):
5220
"""
@@ -72,7 +40,7 @@ def emit(self, record):
7240

7341
# Configure a single logging handle for all `DDAgentLogger` instances to share
7442
_handler = _DDLogHandler() if os.getenv(DD_AGENT_PORT_VAR) else logging.NullHandler()
75-
_handler.setFormatter(_DDLogFormatter())
43+
_handler.setFormatter(DDLogFormatter())
7644

7745
_ddlog = logging.getLogger(_DD_LOG_NAME)
7846
_ddlog.setLevel(logging.INFO)

scfw/loggers/dd_api_logger.py

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,12 @@
1313
from datadog_api_client.v2.model.http_log_item import HTTPLogItem
1414

1515
import scfw
16-
from scfw.configure import DD_API_KEY_VAR
16+
from scfw.configure import DD_API_KEY_VAR, DD_ENV, DD_SERVICE, DD_SOURCE
1717
from scfw.logger import FirewallLogger
18-
from scfw.loggers.dd_logger import DDLogger
18+
from scfw.loggers.dd_logger import DDLogFormatter, DDLogger
1919

2020
_DD_LOG_NAME = "dd_api_log"
2121

22-
_DD_LOG_FORMAT = "%(asctime)s %(levelname)s [%(name)s] [%(filename)s:%(lineno)d] - %(message)s"
23-
2422

2523
class _DDLogHandler(logging.Handler):
2624
"""
@@ -29,36 +27,29 @@ class _DDLogHandler(logging.Handler):
2927
3028
In addition to USM tags, install targets are tagged with the `target` tag and included.
3129
"""
32-
DD_SOURCE = "scfw"
33-
DD_ENV = "dev"
34-
DD_VERSION = scfw.__version__
35-
36-
def __init__(self):
37-
super().__init__()
38-
3930
def emit(self, record):
4031
"""
4132
Format and send a log to Datadog.
4233
4334
Args:
4435
record: The log record to be forwarded.
4536
"""
46-
env = os.getenv("DD_ENV", self.DD_ENV)
47-
service = os.getenv("DD_SERVICE", record.__dict__.get("ecosystem", self.DD_SOURCE))
48-
49-
usm_tags = {f"env:{env}", f"version:{self.DD_VERSION}"}
37+
usm_tags = {
38+
f"env:{os.getenv('DD_ENV', DD_ENV)}",
39+
f"version:{scfw.__version__}"
40+
}
5041

5142
targets = record.__dict__.get("targets", {})
5243
target_tags = set(map(lambda e: f"target:{e}", targets))
5344

5445
body = HTTPLog(
5546
[
5647
HTTPLogItem(
57-
ddsource=self.DD_SOURCE,
48+
ddsource=DD_SOURCE,
5849
ddtags=",".join(usm_tags | target_tags),
5950
hostname=socket.gethostname(),
6051
message=self.format(record),
61-
service=service,
52+
service=DD_SERVICE,
6253
),
6354
]
6455
)
@@ -71,7 +62,7 @@ def emit(self, record):
7162

7263
# Configure a single logging handle for all `DDAPILogger` instances to share
7364
_handler = _DDLogHandler() if os.getenv(DD_API_KEY_VAR) else logging.NullHandler()
74-
_handler.setFormatter(logging.Formatter(_DD_LOG_FORMAT))
65+
_handler.setFormatter(DDLogFormatter())
7566

7667
_ddlog = logging.getLogger(_DD_LOG_NAME)
7768
_ddlog.setLevel(logging.INFO)

scfw/loggers/dd_logger.py

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
Provides a `FirewallLogger` class for sending logs to Datadog.
33
"""
44

5+
import json
56
import logging
67
import os
78

89
import dotenv
910

10-
from scfw.configure import DD_LOG_LEVEL_VAR
11+
import scfw
12+
from scfw.configure import DD_ENV, DD_LOG_LEVEL_VAR, DD_SERVICE, DD_SOURCE
1113
from scfw.ecosystem import ECOSYSTEM
1214
from scfw.logger import FirewallAction, FirewallLogger
1315
from scfw.target import InstallTarget
@@ -20,6 +22,30 @@
2022
dotenv.load_dotenv()
2123

2224

25+
class DDLogFormatter(logging.Formatter):
26+
"""
27+
A custom JSON formatter for firewall logs.
28+
"""
29+
def format(self, record) -> str:
30+
"""
31+
Format a log record as a JSON string.
32+
33+
Args:
34+
record: The log record to be formatted.
35+
"""
36+
log_record = {
37+
"source": DD_SOURCE,
38+
"service": DD_SERVICE,
39+
"version": scfw.__version__,
40+
"env": os.getenv("DD_ENV", DD_ENV),
41+
}
42+
43+
for key in {"action", "created", "ecosystem", "msg", "targets"}:
44+
log_record[key] = record.__dict__[key]
45+
46+
return json.dumps(log_record) + '\n'
47+
48+
2349
class DDLogger(FirewallLogger):
2450
"""
2551
An implementation of `FirewallLogger` for sending logs to Datadog.
@@ -58,15 +84,7 @@ def log(
5884
if not self._level or action < self._level:
5985
return
6086

61-
match action:
62-
case FirewallAction.ALLOW:
63-
message = f"Command '{' '.join(command)}' was allowed"
64-
case FirewallAction.ABORT:
65-
message = f"Command '{' '.join(command)}' was aborted"
66-
case FirewallAction.BLOCK:
67-
message = f"Command '{' '.join(command)}' was blocked"
68-
6987
self._logger.info(
70-
message,
88+
f"Command '{' '.join(command)}' was {str(action).lower()}ed",
7189
extra={"action": str(action), "ecosystem": str(ecosystem), "targets": list(map(str, targets))}
7290
)

0 commit comments

Comments
 (0)