package accesslog import ( "encoding/csv" "io" "strconv" "sync" ) // CSV is a Formatter type for csv encoded logs. type CSV struct { writerPool *sync.Pool ac *AccessLog // Add header fields to the first line if it's not exist. // Note that the destination should be a compatible io.Reader // with access to write. Header bool // Google Spreadsheet's Script to wrap the Timestamp field // in order to convert it into a readable date. // Example: "FROM_UNIX" when // function FROM_UNIX(epoch_in_millis) { // return new Date(epoch_in_millis); // } DateScript string // TODO: Fields []string // field name, position? } // SetOutput initializes the csv writer. // It uses the "dest" as AccessLog to // write the first csv record which // contains the names of the future log values. func (f *CSV) SetOutput(dest io.Writer) { f.ac, _ = dest.(*AccessLog) f.writerPool = &sync.Pool{ New: func() interface{} { return csv.NewWriter(dest) }, } if !f.Header { return } { // If the destination is not a reader // we can't detect if the header already inserted // so we exit, we dont want to malform the contents. destReader, ok := f.ac.Writer.(io.Reader) if !ok { return } r := csv.NewReader(destReader) if header, err := r.Read(); err == nil && len(header) > 0 && header[0] == "Timestamp" { // we assume header already exists, exit. return } } // Write the header. w := csv.NewWriter(dest) keys := []string{"Timestamp", "Latency", "Code", "Method", "Path"} if f.ac.IP { keys = append(keys, "IP") } // keys = append(keys, []string{"Params", "Query"}...) keys = append(keys, "Req Values") if f.ac.BytesReceived || f.ac.BytesReceivedBody { keys = append(keys, "In") } if f.ac.BytesSent || f.ac.BytesSentBody { keys = append(keys, "Out") } if f.ac.RequestBody { keys = append(keys, "Request") } if f.ac.ResponseBody { keys = append(keys, "Response") } w.Write(keys) w.Flush() } // Format writes an incoming log using CSV encoding. func (f *CSV) Format(log *Log) (bool, error) { // Timestamp, Latency, Code, Method, Path, IP, Path Params Query Fields //|Bytes Received|Bytes Sent|Request|Response| timestamp := strconv.FormatInt(log.Timestamp, 10) if f.DateScript != "" { timestamp = "=" + f.DateScript + "(" + timestamp + ")" } values := []string{ timestamp, log.Latency.String(), strconv.Itoa(log.Code), log.Method, log.Path, } if f.ac.IP { values = append(values, log.IP) } if s := log.RequestValuesLine(); s != "" || f.Header { // even if it's empty, if Header was set, then add it. values = append(values, s) } if f.ac.BytesReceived || f.ac.BytesReceivedBody { values = append(values, strconv.Itoa(log.BytesReceived)) } if f.ac.BytesSent || f.ac.BytesSentBody { values = append(values, strconv.Itoa(log.BytesSent)) } if f.ac.RequestBody && (log.Request != "" || f.Header) { values = append(values, log.Request) } if f.ac.ResponseBody && (log.Response != "" || f.Header) { values = append(values, log.Response) } w := f.writerPool.Get().(*csv.Writer) err := w.Write(values) w.Flush() // it works as "reset" too. f.writerPool.Put(w) return true, err }