I want SQL Server to create a log file recording of all (successful) logins/connections to the database. The log should contain minimally:
- IP address and port of the connecting client
- application name of the client
- username
- database name
- time of connection or applicable transaction
It looks like a server trace likely can capture all this information, but Microsoft says that server traces are deprecated in favor of Extended Events.
So I am attempting to gather this information with Extended Events. For my tests I'm using SQL Server 2017 Developer Edition (running in a Docker container on Windows 10) and SSMS v17.7. I'm logged in as 'sa' when I create or view the extended events.
So far I've been able to collect most of this information with Extended Events. The problem is collecting the client IP and port. I can get either part in isolation, but not both simultaneously. The XEvents I'm using are listed below. The fact that connection_accept
is listed twice is not a mistake. SQL Server actually has two different events with the exact same name(!!!).
Login
: no option (field or action) to collect client IP or port. At least it does offerclient_hostname
!Logout
: no option (field or action) to collect client IP or port. This also capturesclient_hostname
.connection_accept
:- Collects client IP, but it masks the lowest octet (e.g. 192.168.1.XX)!!!
- Collects client port! Good!
- Does not collect session_id, so it cannot be correlated with
Login
orLogout
events. - My
EVENT SESSION
specifies collection ofusername
,client_app_name
andclient_hostname
, but none of these fields/actions are present in the collected data. :(
connection_accept
:- Collects client port, but does not collect client IP!!!
- Has sesstion_id, so at least it can be correlated with
Login
andLogout
- Note: I've not yet caught this particular event in the wild, so I have no further comments about it.
None of these events provide the IP address of the client, but I will accept the client hostname as a reasonable substitute. However, attaining the port number is a real problem. The port number is only found in the connection_accept
event, and there is no evident way to correlate that to the login
event which has the hostname. In short, it seems that Extended Events simply is unable to deliver this basic client IP-and-port pairing. I want to believe I'm wrong, because it is such elementary data. Any help or suggestions of what I'm overlooking would be much appreciated.
Extended Event DDL
Here is the DDL for the EVENT SESSION
I've been testing:
CREATE EVENT SESSION [Connections] ON SERVER
ADD EVENT SQLSatellite.connection_accept(
ACTION(sqlserver.client_app_name,sqlserver.client_connection_id,sqlserver.client_hostname,sqlserver.context_info,sqlserver.database_name,sqlserver.nt_username,sqlserver.request_id,sqlserver.server_instance_name,sqlserver.server_principal_name,sqlserver.session_id,sqlserver.transaction_id,sqlserver.username)
WHERE ([sqlserver].[not_equal_i_sql_unicode_string]([sqlserver].[client_app_name],N'SQLServerCEIP'))),
ADD EVENT sqlserver.connection_accept(
ACTION(sqlserver.client_app_name,sqlserver.client_connection_id,sqlserver.client_hostname,sqlserver.context_info,sqlserver.database_name,sqlserver.nt_username,sqlserver.request_id,sqlserver.server_instance_name,sqlserver.server_principal_name,sqlserver.session_id,sqlserver.transaction_id,sqlserver.username)
WHERE ([sqlserver].[not_equal_i_sql_unicode_string]([sqlserver].[client_app_name],N'SQLServerCEIP'))),
ADD EVENT sqlserver.connectivity_ring_buffer_recorded(
ACTION(sqlserver.client_app_name,sqlserver.client_connection_id,sqlserver.client_hostname,sqlserver.database_name,sqlserver.nt_username,sqlserver.server_instance_name,sqlserver.server_principal_name,sqlserver.session_id,sqlserver.session_nt_username,sqlserver.transaction_id,sqlserver.username)
WHERE ([sqlserver].[client_app_name]<>N'SQLServerCEIP')),
ADD EVENT sqlserver.login(SET collect_options_text=(1)
ACTION(sqlserver.client_app_name,sqlserver.client_connection_id,sqlserver.client_hostname,sqlserver.database_name,sqlserver.nt_username,sqlserver.server_instance_name,sqlserver.server_principal_name,sqlserver.session_id,sqlserver.session_nt_username,sqlserver.transaction_id,sqlserver.username)
WHERE ([sqlserver].[client_app_name]<>N'SQLServerCEIP')),
ADD EVENT sqlserver.logout(
ACTION(sqlserver.client_app_name,sqlserver.client_connection_id,sqlserver.client_hostname,sqlserver.database_name,sqlserver.nt_username,sqlserver.server_instance_name,sqlserver.server_principal_name,sqlserver.session_id,sqlserver.session_nt_username,sqlserver.transaction_id,sqlserver.username)
WHERE ([sqlserver].[client_app_name]<>N'SQLServerCEIP'))
ADD TARGET package0.event_file(SET filename=N'c:\xevents\connections')
WITH (MAX_MEMORY=4096 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,MAX_DISPATCH_LATENCY=30 SECONDS,MAX_EVENT_SIZE=0 KB,MEMORY_PARTITION_MODE=NONE,TRACK_CAUSALITY=ON,STARTUP_STATE=OFF)
GO
Alternatives
Aside from Extended Events, "security audits" is another mechanism that is not being deprecated. So I thought I'd also give this a try:
USE [master]
GO
CREATE SERVER AUDIT [AuditTest]
TO FILE
( FILEPATH = N'C:\xevents\'
,MAXSIZE = 2 MB
,MAX_FILES = 5
,RESERVE_DISK_SPACE = OFF
)
WITH
( QUEUE_DELAY = 1000
,ON_FAILURE = CONTINUE
)
CREATE SERVER AUDIT SPECIFICATION [Connections]
FOR SERVER AUDIT [AuditTest]
ADD (SUCCESSFUL_LOGIN_GROUP),
ADD (LOGOUT_GROUP)
GO
Among the fields this provided were "client app", "client IP", "Session ID" and username (viz. "server principle name"). However, there is no way to correlate this back to the connection_accept
XEvent. So there is no way to extract the two pieces of information I want: client IP and port number. :(
Bonus
Aside from collecting client IP and port, I'm also interested in collecting SQL Server network traffic volume. However, I've yet to see any way to do that either. Tips appreciated! I'm only seeing vaguely related posts.
Update
I've noticed a global action (field) called task_address
that appears to correlate login
, logout
and connection_accept
events. Thus it may be possible now to find the IP and port by combining login:client_hostname
and connection_accept:port
. My only concern is that I can't find documentation for task_address
to validate my observations.