1+ const { Werelogs } = require ( 'werelogs' ) ;
2+ const { config } = require ( '../Config' ) ;
3+ const fs = require ( 'fs' ) ;
4+ const path = require ( 'path' ) ;
5+
6+ const DEFAULT_OUTPUT_FILE = './logs/api-operations.log' ;
7+
8+ function createServerAccessLogger ( ) {
9+ if ( ! config . serverAccessLogs || ! config . serverAccessLogs . enabled ) {
10+ console . warn ( "ServerAccessLogs disabled returning no-op logger" ) ;
11+ return {
12+ info : ( ) => { } ,
13+ debug : ( ) => { } ,
14+ warn : ( ) => { } ,
15+ error : ( ) => { } ,
16+ trace : ( ) => { } ,
17+ fatal : ( ) => { } ,
18+ } ;
19+ }
20+
21+ // Ensure logs directory exists
22+ const outputFile = config . serverAccessLogs . outputFile || DEFAULT_OUTPUT_FILE ;
23+ const logDir = path . dirname ( outputFile ) ;
24+
25+ try {
26+ if ( ! fs . existsSync ( logDir ) ) {
27+ fs . mkdirSync ( logDir , { recursive : true } ) ;
28+ }
29+ } catch ( error ) {
30+ // Fall back to console-only logging if directory creation fails
31+ console . warn ( 'Failed to create ServerAccess log directory, falling back to console logging:' , error . message ) ;
32+
33+ let apiWerelogs = new Werelogs ( {
34+ level : config . serverAccessLogs . logLevel || 'info' ,
35+ dump : config . serverAccessLogs . dumpLevel || 'error' ,
36+ streams : [
37+ { level : 'trace' , stream : process . stdout }
38+ ]
39+ } ) ;
40+
41+ return new apiWerelogs . Logger ( 'ServerAccessLogger' ) ;
42+ }
43+
44+ // Create file stream for API logs
45+ const serverAccessLogStream = fs . createWriteStream ( outputFile , { flags : 'a' } ) ;
46+
47+ // Handle stream errors
48+ serverAccessLogStream . on ( 'error' , error => {
49+ console . error ( 'ServerAccessLogger log file stream error:' , error ) ;
50+ } ) ;
51+
52+ // Create the API-specific Werelogs instance - file output only
53+ apiWerelogs = new Werelogs ( {
54+ level : config . serverAccessLogs . logLevel || 'info' ,
55+ dump : config . serverAccessLogs . dumpLevel || 'error' ,
56+ streams : [ { level : 'trace' , stream : serverAccessLogStream } ]
57+ } ) ;
58+ console . info ( "ServerAccessLogger created successfully" ) ;
59+ return new apiWerelogs . Logger ( 'ServerAccessLogger' ) ;
60+ }
61+
62+ var serverAccessLogger = {
63+ info : ( ) => { } ,
64+ debug : ( ) => { } ,
65+ warn : ( ) => { } ,
66+ error : ( ) => { } ,
67+ trace : ( ) => { } ,
68+ fatal : ( ) => { } ,
69+ } ;
70+
71+
72+ try {
73+ serverAccessLogger = createServerAccessLogger ( ) ;
74+ } catch ( error ) {
75+ console . error ( 'Failed to create ServiceAccessLogger, using no-op logger:' , error ) ;
76+ }
77+
78+ function getRemoteIPFromRequest ( request ) {
79+ let remoteIP = '-' ;
80+ if ( request . headers ) {
81+ // Check for forwarded IP headers (proxy/load balancer scenarios)
82+ remoteIP = request . headers [ 'x-forwarded-for' ] ||
83+ request . headers [ 'x-real-ip' ] ||
84+ request . headers [ 'x-client-ip' ] ||
85+ request . headers [ 'cf-connecting-ip' ] ; // Cloudflare
86+
87+ // x-forwarded-for can contain multiple IPs, take the first one
88+ if ( remoteIP && remoteIP . includes ( ',' ) ) {
89+ remoteIP = remoteIP . split ( ',' ) [ 0 ] . trim ( ) ;
90+ }
91+ }
92+
93+ // Fallback to connection remote address if no forwarded headers
94+ if ( ! remoteIP || remoteIP === '-' ) {
95+ remoteIP = ( request . connection && request . connection . remoteAddress ) ||
96+ ( request . socket && request . socket . remoteAddress ) ||
97+ ( request . ip ) ||
98+ '-' ;
99+ }
100+
101+ return remoteIP ;
102+ }
103+
104+ function getOperation ( req ) {
105+ const methodToResType = Object . freeze ( {
106+ 'bucketDelete' : 'BUCKET' ,
107+ 'bucketDeleteCors' : 'BUCKET' ,
108+ 'bucketDeleteEncryption' : 'BUCKET' ,
109+ 'bucketDeleteWebsite' : 'BUCKET' ,
110+ 'bucketGet' : 'BUCKET' ,
111+ 'bucketGetACL' : 'BUCKET' ,
112+ 'bucketGetCors' : 'BUCKET' ,
113+ 'bucketGetObjectLock' : 'BUCKET' ,
114+ 'bucketGetVersioning' : 'VERSIONING' ,
115+ 'bucketGetWebsite' : 'BUCKET' ,
116+ 'bucketGetLocation' : 'BUCKET' ,
117+ 'bucketGetEncryption' : 'BUCKET' ,
118+ 'bucketHead' : 'BUCKET' ,
119+ 'bucketPut' : 'BUCKET' ,
120+ 'bucketPutACL' : 'BUCKET' ,
121+ 'bucketPutCors' : 'BUCKET' ,
122+ 'bucketPutVersioning' : 'VERSIONING' ,
123+ 'bucketPutTagging' : 'BUCKET' ,
124+ 'bucketDeleteTagging' : 'BUCKET' ,
125+ 'bucketGetTagging' : 'BUCKET' ,
126+ 'bucketPutWebsite' : 'BUCKET' ,
127+ 'bucketPutReplication' : 'BUCKET' ,
128+ 'bucketGetReplication' : 'BUCKET' ,
129+ 'bucketDeleteReplication' : 'BUCKET' ,
130+ 'bucketDeleteQuota' : 'BUCKET' ,
131+ 'bucketPutLifecycle' : 'BUCKET' ,
132+ 'bucketUpdateQuota' : 'BUCKET' ,
133+ 'bucketGetLifecycle' : 'BUCKET' ,
134+ 'bucketDeleteLifecycle' : 'BUCKET' ,
135+ 'bucketPutPolicy' : 'BUCKETPOLICY' ,
136+ 'bucketGetPolicy' : 'BUCKETPOLICY' ,
137+ 'bucketGetQuota' : 'BUCKET' ,
138+ 'bucketDeletePolicy' : 'BUCKETPOLICY' ,
139+ 'bucketPutObjectLock' : 'BUCKET' ,
140+ 'bucketPutNotification' : 'BUCKET' ,
141+ 'bucketGetNotification' : 'BUCKET' ,
142+ 'bucketPutEncryption' : 'BUCKET' ,
143+ 'bucketPutLogging' : 'LOGGING_STATUS' ,
144+ 'bucketGetLogging' : 'LOGGING_STATUS' ,
145+ // 'corsPreflight': '',
146+ 'completeMultipartUpload' : 'OBJECT' ,
147+ 'initiateMultipartUpload' : 'OBJECT' ,
148+ 'listMultipartUploads' : 'OBJECT' ,
149+ 'listParts' : 'OBJECT' ,
150+ 'metadataSearch' : 'OBJECT' ,
151+ 'multiObjectDelete' : 'OBJECT' ,
152+ 'multipartDelete' : 'OBJECT' ,
153+ 'objectDelete' : 'OBJECT' ,
154+ 'objectDeleteTagging' : 'OBJECT' ,
155+ 'objectGet' : 'OBJECT' ,
156+ 'objectGetACL' : 'OBJECT' ,
157+ 'objectGetLegalHold' : 'OBJECT' ,
158+ 'objectGetRetention' : 'OBJECT' ,
159+ 'objectGetTagging' : 'OBJECT' ,
160+ 'objectCopy' : 'OBJECT' ,
161+ 'objectHead' : 'OBJECT' ,
162+ 'objectPut' : 'OBJECT' ,
163+ 'objectPutACL' : 'OBJECT' ,
164+ 'objectPutLegalHold' : 'OBJECT' ,
165+ 'objectPutTagging' : 'OBJECT' ,
166+ 'objectPutPart' : 'OBJECT' ,
167+ 'objectPutCopyPart' : 'OBJECT' ,
168+ 'objectPutRetention' : 'OBJECT' ,
169+ 'objectRestore' : 'OBJECT' ,
170+ // 'serviceGet': '',
171+ // 'websiteGet': '',
172+ // 'websiteHead': '',
173+ } ) ;
174+
175+ return `REST.${ req . method } .${ methodToResType [ req . apiMethod ] ? methodToResType [ req . apiMethod ] : 'UNKNOWN' } `
176+ }
177+
178+ function getRequester ( authInfo ) {
179+ let requester = '-' ;
180+ if ( authInfo ) {
181+ if ( authInfo . isRequesterPublicUser && authInfo . isRequesterPublicUser ( ) ) {
182+ return requester ; // Unauthenticated requests
183+ } else if ( authInfo . isRequesterAnIAMUser && authInfo . isRequesterAnIAMUser ( ) ) {
184+ // IAM user: include IAM user name and account
185+ const iamUserName = authInfo . getIAMdisplayName ? authInfo . getIAMdisplayName ( ) : '' ;
186+ const accountName = authInfo . getAccountDisplayName ? authInfo . getAccountDisplayName ( ) : '' ;
187+ return iamUserName && accountName ? `${ iamUserName } :${ accountName } ` : authInfo . getCanonicalID ( ) ;
188+ } else if ( authInfo . getCanonicalID ) {
189+ // Regular user: canonical user ID
190+ return authInfo . getCanonicalID ( ) ;
191+ }
192+ }
193+ return requester ;
194+ }
195+
196+ function getURI ( request ) {
197+ let requestURI = '-' ;
198+ if ( request ) {
199+ const method = request . method || 'UNKNOWN' ;
200+ const url = request . url || request . originalUrl || '/' ;
201+ const httpVersion = request . httpVersion || '1.1' ;
202+ requestURI = `${ method } ${ url } HTTP/${ httpVersion } ` ;
203+ }
204+ return requestURI ;
205+ }
206+
207+ function getObjectSize ( request ) {
208+ const objectSizeMethods = Object . freeze ( {
209+ 'objectPut' : true ,
210+ 'objectPutPart' : true ,
211+ 'objectGet' : true ,
212+ } ) ;
213+
214+ if ( request && objectSizeMethods [ request . apiMethod ] ) {
215+ const len = request . getHeader ( 'Content-Length' ) ;
216+ return len ? len : '-' ;
217+ }
218+
219+ return '-' ;
220+ }
221+
222+ function getBytesSent ( res ) {
223+ if ( ! res ) {
224+ return '-' ;
225+ }
226+ if ( res . getHeader ( 'Content-Length' ) ) {
227+ return res . getHeader ( 'Content-Length' ) ;
228+ }
229+ return '-' ;
230+ }
231+
232+ function logServerAccess ( params , req , res ) {
233+ if ( ! params . enabled ) {
234+ return ;
235+ }
236+
237+ console . log ( params )
238+
239+ serverAccessLogger . info ( 'SERVER_ACCESS_LOG' , {
240+ // AWS fields.
241+ bucketOwner : params . bucketOwner ,
242+ bucket : params . bucketName ,
243+ startTime : params . startTime . toString ( ) ,
244+ remoteIP : getRemoteIPFromRequest ( req ) ,
245+ requester : getRequester ( params . authInfo ) ,
246+ // // requestID: '-', // From wherelogs.
247+ operation : getOperation ( req ) ,
248+ requestURI : getURI ( req ) ,
249+ HTTPStatus : res . statusCode ,
250+ errorCode : params . errorCode ? params . errorCode : '-' ,
251+ bytesSent : getBytesSent ( res ) ,
252+ objectSize : getObjectSize ( req ) ,
253+ totalTime : ( Number ( params . endTime - params . startTime ) / 1_000_000 ) . toString ( ) ,
254+ turnAroundTime : ( Number ( params . endTurnAroundTime - params . startTurnAroundTime ) / 1_000_000 ) . toString ( ) ,
255+ referer : req . headers . referer ? req . headers . referer : '-' ,
256+ userAgent : req . headers [ 'user-agent' ] ? req . headers [ 'user-agent' ] : '-' ,
257+ versionID : req . query . versionId ? req . query . versionId : '-' , // query inserted by arsenal.
258+ hostID : '-' , // NOT IMPLEMENTED
259+ signatureVersion : params . authInfo . getAuthVersion ( ) ,
260+ cipherSuite : req . socket . encrypted ? req . socket . getCipher ( ) [ 'standardName' ] : '-' ,
261+ authenticationType : params . authInfo . getAuthType ( ) ,
262+ hostHeader : req . headers . host ? req . headers . host : '-' ,
263+ // From https://nodejs.org/api/tls.html#tlssocketgetcipher
264+ tlsVersion : req . socket . encrypted ? req . socket . getCipher ( ) [ 'version' ] : '-' ,
265+ accessPointARN : '-' , // NOT IMPLEMENTED
266+ aclRequired : '-' , // ???
267+ // Scality extra fields.
268+ logFormatVersion : '-' ,
269+ loggingEnabled : true ,
270+ loggingTargetBucket : params . loggingEnabled . TargetBucket ,
271+ loggingTargetPrefix : params . loggingEnabled . TargetPrefix ,
272+ raftSessionID : '-' ,
273+ aws_access_key_id : params . authInfo . getAccessKey ( ) ? params . authInfo . getAccessKey ( ) : '-' ,
274+ } )
275+ }
276+
277+ module . exports = {
278+ logServerAccess,
279+ } ;
0 commit comments