Skip to content

Commit 62ccba0

Browse files
authored
Add initialization health event to SQLServer (DataDog#21868)
* Add initialization health event to SQLServer * Changelog * Bump base version * Base event * Tags * Fix timestamp
1 parent 13fc336 commit 62ccba0

5 files changed

Lines changed: 111 additions & 3 deletions

File tree

sqlserver/changelog.d/21868.added

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add DBM agent health events to SQLServer. Events include basic initialization check, unhandled error, and missed collection.

sqlserver/datadog_checks/sqlserver/config.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# All rights reserved
33
# Licensed under a 3-clause BSD style license (see LICENSE)
44

5+
import copy
56
import json
67
import re
78

@@ -274,3 +275,12 @@ def _build_database_metrics_configs(self, instance):
274275
if value is not None:
275276
config[key] = value
276277
return configurable_metrics
278+
279+
280+
def sanitize(config: dict) -> dict:
281+
"""
282+
Sanitize the config to remove sensitive information.
283+
"""
284+
sanitized = copy.deepcopy(config)
285+
sanitized['password'] = '***' if sanitized.get('password') else None
286+
return sanitized
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# (C) Datadog, Inc. 2025-present
2+
# All rights reserved
3+
# Licensed under a 3-clause BSD style license (see LICENSE)
4+
from __future__ import annotations
5+
6+
from typing import TYPE_CHECKING
7+
8+
if TYPE_CHECKING:
9+
from datadog_checks.sqlserver import SqlServer
10+
11+
from enum import Enum
12+
13+
from datadog_checks.base.utils.db.health import Health, HealthEvent, HealthStatus
14+
15+
16+
class SqlServerHealthEvent(Enum):
17+
"""
18+
Enum representing the health events for SqlServer monitoring.
19+
"""
20+
21+
EXPLAIN_PLAN_ERROR = 'explain_plan_error'
22+
23+
24+
class SqlServerHealth(Health):
25+
def __init__(self, check: SqlServer):
26+
# type: (SqlServer) -> None
27+
"""
28+
Initialize the SqlServerHealth instance.
29+
30+
:param check: SqlServer
31+
The check instance that will be used to submit health events.
32+
"""
33+
super().__init__(check)
34+
self.check = check
35+
36+
def submit_health_event(
37+
self,
38+
name: HealthEvent | SqlServerHealthEvent,
39+
status: HealthStatus,
40+
tags: list[str] = None,
41+
data: dict = None,
42+
**kwargs,
43+
):
44+
"""
45+
Submit a health event to the aggregator.
46+
47+
:param name: SqlServerHealthEvent
48+
The name of the health event.
49+
:param status: HealthStatus
50+
The health status to submit.
51+
:param data: A dictionary to be submitted as `data`. Must be JSON serializable.
52+
"""
53+
super().submit_health_event(
54+
name=name,
55+
status=status,
56+
# If we have an error parsing the config we may not have tags yet
57+
tags=(self.check.tags if hasattr(self.check, 'tags') else []) + (tags or []),
58+
data={
59+
"database_instance": self.check.database_identifier,
60+
"ddagenthostname": self.check.agent_hostname,
61+
**(data or {}),
62+
},
63+
**kwargs,
64+
)

sqlserver/datadog_checks/sqlserver/sqlserver.py

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111
from cachetools import TTLCache
1212

1313
from datadog_checks.base import AgentCheck
14+
from datadog_checks.base.checks.db import DatabaseCheck
1415
from datadog_checks.base.config import is_affirmative
1516
from datadog_checks.base.utils.db import QueryExecutor, QueryManager
17+
from datadog_checks.base.utils.db.health import HealthEvent, HealthStatus
1618
from datadog_checks.base.utils.db.utils import (
1719
TagManager,
1820
default_json_event_encoding,
@@ -49,15 +51,18 @@
4951
SQLServerXESessionMetrics,
5052
)
5153
from datadog_checks.sqlserver.deadlocks import Deadlocks
54+
from datadog_checks.sqlserver.health import SqlServerHealth
5255
from datadog_checks.sqlserver.metadata import SqlserverMetadata
5356
from datadog_checks.sqlserver.schemas import Schemas
5457
from datadog_checks.sqlserver.statements import SqlserverStatementMetrics
5558
from datadog_checks.sqlserver.stored_procedures import SqlserverProcedureMetrics
5659
from datadog_checks.sqlserver.utils import Database, construct_use_statement, parse_sqlserver_major_version
5760
from datadog_checks.sqlserver.xe_collection.registry import get_xe_session_handlers
5861

62+
from .config import sanitize
63+
5964
try:
60-
import datadog_agent
65+
import datadog_agent # type: ignore
6166
except ImportError:
6267
from datadog_checks.base.stubs import datadog_agent
6368

@@ -116,14 +121,16 @@
116121
set_default_driver_conf()
117122

118123

119-
class SQLServer(AgentCheck):
124+
class SQLServer(DatabaseCheck):
120125
__NAMESPACE__ = "sqlserver"
121126

122127
HA_SUPPORTED = True
123128

124129
def __init__(self, name, init_config, instances):
125130
super(SQLServer, self).__init__(name, init_config, instances)
126131

132+
self.health = SqlServerHealth(self)
133+
127134
self.static_info_cache = TTLCache(
128135
maxsize=100,
129136
# cache these for a full day
@@ -141,6 +148,8 @@ def __init__(self, name, init_config, instances):
141148
self.do_check = True
142149

143150
self._config = SQLServerConfig(self.init_config, self.instance, self.log)
151+
self._initialized_at = int(time.time() * 1000)
152+
144153
self.cloud_metadata = self._config.cloud_metadata
145154
self.tag_manager = TagManager(normalizer=lambda tag: self.normalize_tag(tag).lower())
146155
self.tag_manager.set_tags_from_list(self._config.tags, replace=True) # Initialize from static config tags
@@ -183,6 +192,8 @@ def __init__(self, name, init_config, instances):
183192

184193
self._schemas = Schemas(self, self._config)
185194

195+
self._submit_initialization_health_event()
196+
186197
def initialize_xe_session_handlers(self):
187198
"""Initialize the XE session handlers without starting them"""
188199
# Initialize XE session handlers if not already initialized
@@ -218,6 +229,22 @@ def config_checks(self):
218229
"Autodiscovery is disabled, autodiscovery_include and autodiscovery_exclude will be ignored"
219230
)
220231

232+
def _submit_initialization_health_event(self):
233+
try:
234+
# Handle the config validation result after we've set tags so those tags are included in the health event
235+
# TODO: Validate the config once it has been refactored
236+
self.health.submit_health_event(
237+
name=HealthEvent.INITIALIZATION,
238+
status=HealthStatus.OK,
239+
cooldown_time=60 * 60 * 6, # 6 hours
240+
data={
241+
"initialized_at": self._initialized_at,
242+
"instance": sanitize(self.instance),
243+
},
244+
)
245+
except Exception as e:
246+
self.log.error("Error submitting health event for initialization: %s", e)
247+
221248
def _new_query_executor(self, queries, executor, extra_tags=None, track_operation_time=False):
222249
tags = self.tag_manager.get_tags() + (extra_tags or [])
223250
return QueryExecutor(
@@ -299,6 +326,10 @@ def port(self):
299326
def resolve_db_host(self):
300327
return agent_host_resolver(self.host)
301328

329+
@property
330+
def tags(self):
331+
return self.tag_manager.get_tags()
332+
302333
@property
303334
def reported_hostname(self):
304335
# type: () -> str
@@ -821,6 +852,8 @@ def _check_database_conns(self):
821852
self._check_connections_by_use_db()
822853

823854
def check(self, _):
855+
self._submit_initialization_health_event()
856+
824857
if self.do_check:
825858
self.load_static_information()
826859
# configure custom queries for the check

sqlserver/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ classifiers = [
2828
"Private :: Do Not Upload",
2929
]
3030
dependencies = [
31-
"datadog-checks-base>=37.21.0",
31+
"datadog-checks-base>=37.22.1",
3232
]
3333
dynamic = [
3434
"version",

0 commit comments

Comments
 (0)