dubbo.apache.org/dubbo-go/v3@v3.1.1/filter/accesslog/filter.go (about) 1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 // Package accesslog providers logging filter. 19 package accesslog 20 21 import ( 22 "context" 23 "os" 24 "reflect" 25 "strings" 26 "sync" 27 "time" 28 ) 29 30 import ( 31 "github.com/dubbogo/gost/log/logger" 32 ) 33 34 import ( 35 "dubbo.apache.org/dubbo-go/v3/common/constant" 36 "dubbo.apache.org/dubbo-go/v3/common/extension" 37 "dubbo.apache.org/dubbo-go/v3/filter" 38 "dubbo.apache.org/dubbo-go/v3/protocol" 39 ) 40 41 const ( 42 // used in URL. 43 // nolint 44 FileDateFormat = "2006-01-02" 45 // nolint 46 MessageDateLayout = "2006-01-02 15:04:05" 47 // nolint 48 LogMaxBuffer = 5000 49 // nolint 50 LogFileMode = 0o600 51 52 // those fields are the data collected by this filter 53 54 // nolint 55 Types = "types" 56 // nolint 57 Arguments = "arguments" 58 ) 59 60 var ( 61 once sync.Once 62 accessLogFilter *Filter 63 ) 64 65 func init() { 66 extension.SetFilter(constant.AccessLogFilterKey, newFilter) 67 } 68 69 // Filter for Access Log 70 /** 71 * Although the access log filter is a default filter, 72 * you should config "accesslog" in service's config to tell the filter where store the access log. 73 * for example: 74 * "UserProvider": 75 * registry: "hangzhouzk" 76 * protocol : "dubbo" 77 * interface : "com.ikurento.user.UserProvider" 78 * ... # other configuration 79 * accesslog: "/your/path/to/store/the/log/", # it should be the path of file. 80 * 81 * the value of "accesslog" can be "true" or "default" too. 82 * If the value is one of them, the access log will be record in log file which defined in log.yml 83 * AccessLogFilter is designed to be singleton 84 */ 85 type Filter struct { 86 logChan chan Data 87 } 88 89 func newFilter() filter.Filter { 90 if accessLogFilter == nil { 91 once.Do(func() { 92 accessLogFilter = &Filter{logChan: make(chan Data, LogMaxBuffer)} 93 go func() { 94 for accessLogData := range accessLogFilter.logChan { 95 accessLogFilter.writeLogToFile(accessLogData) 96 } 97 }() 98 }) 99 } 100 return accessLogFilter 101 } 102 103 // Invoke will check whether user wants to use this filter. 104 // If we find the value of key constant.AccessLogFilterKey, we will log the invocation info 105 func (f *Filter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { 106 accessLog := invoker.GetURL().GetParam(constant.AccessLogFilterKey, "") 107 108 // the user do not 109 if len(accessLog) > 0 { 110 accessLogData := Data{data: f.buildAccessLogData(invoker, invocation), accessLog: accessLog} 111 f.logIntoChannel(accessLogData) 112 } 113 return invoker.Invoke(ctx, invocation) 114 } 115 116 // logIntoChannel won't block the invocation 117 func (f *Filter) logIntoChannel(accessLogData Data) { 118 select { 119 case f.logChan <- accessLogData: 120 return 121 default: 122 logger.Warn("The channel is full and the access logIntoChannel data will be dropped") 123 return 124 } 125 } 126 127 // buildAccessLogData builds the access log data 128 func (f *Filter) buildAccessLogData(_ protocol.Invoker, invocation protocol.Invocation) map[string]string { 129 dataMap := make(map[string]string, 16) 130 attachments := invocation.Attachments() 131 itf := attachments[constant.InterfaceKey] 132 if itf == nil || len(itf.(string)) == 0 { 133 itf = attachments[constant.PathKey] 134 } 135 if itf != nil { 136 dataMap[constant.InterfaceKey] = itf.(string) 137 } 138 if v, ok := attachments[constant.MethodKey]; ok && v != nil { 139 dataMap[constant.MethodKey] = v.(string) 140 } 141 if v, ok := attachments[constant.VersionKey]; ok && v != nil { 142 dataMap[constant.VersionKey] = v.(string) 143 } 144 if v, ok := attachments[constant.GroupKey]; ok && v != nil { 145 dataMap[constant.GroupKey] = v.(string) 146 } 147 if v, ok := attachments[constant.TimestampKey]; ok && v != nil { 148 dataMap[constant.TimestampKey] = v.(string) 149 } 150 if v, ok := attachments[constant.LocalAddr]; ok && v != nil { 151 dataMap[constant.LocalAddr] = v.(string) 152 } 153 if v, ok := attachments[constant.RemoteAddr]; ok && v != nil { 154 dataMap[constant.RemoteAddr] = v.(string) 155 } 156 157 if len(invocation.Arguments()) > 0 { 158 builder := strings.Builder{} 159 // todo(after the paramTypes were set to the invocation. we should change this implementation) 160 typeBuilder := strings.Builder{} 161 162 builder.WriteString(reflect.ValueOf(invocation.Arguments()[0]).String()) 163 typeBuilder.WriteString(reflect.TypeOf(invocation.Arguments()[0]).Name()) 164 for idx := 1; idx < len(invocation.Arguments()); idx++ { 165 arg := invocation.Arguments()[idx] 166 builder.WriteString(",") 167 builder.WriteString(reflect.ValueOf(arg).String()) 168 169 typeBuilder.WriteString(",") 170 typeBuilder.WriteString(reflect.TypeOf(arg).Name()) 171 } 172 dataMap[Arguments] = builder.String() 173 dataMap[Types] = typeBuilder.String() 174 } 175 176 return dataMap 177 } 178 179 // OnResponse do nothing 180 func (f *Filter) OnResponse(_ context.Context, result protocol.Result, _ protocol.Invoker, _ protocol.Invocation) protocol.Result { 181 return result 182 } 183 184 // writeLogToFile actually write the logs into file 185 func (f *Filter) writeLogToFile(data Data) { 186 accessLog := data.accessLog 187 if isDefault(accessLog) { 188 logger.Info(data.toLogMessage()) 189 return 190 } 191 192 logFile, err := f.openLogFile(accessLog) 193 if err != nil { 194 logger.Warnf("Can not open the access log file: %s, %v", accessLog, err) 195 return 196 } 197 logger.Debugf("Append log to %s", accessLog) 198 message := data.toLogMessage() 199 message = message + "\n" 200 _, err = logFile.WriteString(message) 201 if err != nil { 202 logger.Warnf("Can not write the log into access log file: %s, %v", accessLog, err) 203 } 204 } 205 206 // openLogFile will open the log file with append mode. 207 // If the file is not found, it will create the file. 208 // Actually, the accessLog is the filename 209 // You may find out that, once we want to write access log into log file, 210 // we open the file again and again. 211 // It needs to be optimized. 212 func (f *Filter) openLogFile(accessLog string) (*os.File, error) { 213 logFile, err := os.OpenFile(accessLog, os.O_CREATE|os.O_APPEND|os.O_RDWR, LogFileMode) 214 if err != nil { 215 logger.Warnf("Can not open the access log file: %s, %v", accessLog, err) 216 return nil, err 217 } 218 now := time.Now().Format(FileDateFormat) 219 fileInfo, err := logFile.Stat() 220 if err != nil { 221 logger.Warnf("Can not get the info of access log file: %s, %v", accessLog, err) 222 return nil, err 223 } 224 last := fileInfo.ModTime().Format(FileDateFormat) 225 226 // this is confused. 227 // for example, if the last = '2020-03-04' 228 // and today is '2020-03-05' 229 // we will create one new file to log access data 230 // By this way, we can split the access log based on days. 231 if now != last { 232 err = os.Rename(fileInfo.Name(), fileInfo.Name()+"."+now) 233 if err != nil { 234 logger.Warnf("Can not rename access log file: %s, %v", fileInfo.Name(), err) 235 return nil, err 236 } 237 logFile, err = os.OpenFile(accessLog, os.O_CREATE|os.O_APPEND|os.O_RDWR, LogFileMode) 238 } 239 return logFile, err 240 } 241 242 // isDefault check whether accessLog == true or accessLog == default 243 func isDefault(accessLog string) bool { 244 return strings.EqualFold("true", accessLog) || strings.EqualFold("default", accessLog) 245 } 246 247 // Data defines the data that will be log into file 248 type Data struct { 249 accessLog string 250 data map[string]string 251 } 252 253 // toLogMessage convert the Data to String 254 func (d *Data) toLogMessage() string { 255 builder := strings.Builder{} 256 builder.WriteString("[") 257 builder.WriteString(d.data[constant.TimestampKey]) 258 builder.WriteString("] ") 259 builder.WriteString(d.data[constant.RemoteAddr]) 260 builder.WriteString(" -> ") 261 builder.WriteString(d.data[constant.LocalAddr]) 262 builder.WriteString(" - ") 263 if len(d.data[constant.GroupKey]) > 0 { 264 builder.WriteString(d.data[constant.GroupKey]) 265 builder.WriteString("/") 266 } 267 268 builder.WriteString(d.data[constant.InterfaceKey]) 269 270 if len(d.data[constant.VersionKey]) > 0 { 271 builder.WriteString(":") 272 builder.WriteString(d.data[constant.VersionKey]) 273 } 274 275 builder.WriteString(" ") 276 builder.WriteString(d.data[constant.MethodKey]) 277 builder.WriteString("(") 278 if len(d.data[Types]) > 0 { 279 builder.WriteString(d.data[Types]) 280 } 281 builder.WriteString(") ") 282 283 if len(d.data[Arguments]) > 0 { 284 builder.WriteString(d.data[Arguments]) 285 } 286 return builder.String() 287 }