1// Copyright 2018 The Go Cloud Development Kit Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package requestlog
16
17import (
18	"io"
19	"strconv"
20	"sync"
21)
22
23// An NCSALogger writes log entries to an io.Writer in the
24// Combined Log Format.
25//
26// Details at http://httpd.apache.org/docs/current/logs.html#combined
27type NCSALogger struct {
28	onErr func(error)
29
30	mu  sync.Mutex
31	w   io.Writer
32	buf []byte
33}
34
35// NewNCSALogger returns a new logger that writes to w.
36// A nil onErr is treated the same as func(error) {}.
37func NewNCSALogger(w io.Writer, onErr func(error)) *NCSALogger {
38	return &NCSALogger{
39		w:     w,
40		onErr: onErr,
41	}
42}
43
44// Log writes an entry line to its writer.  Multiple concurrent calls
45// will produce sequential writes to its writer.
46func (l *NCSALogger) Log(ent *Entry) {
47	if err := l.log(ent); err != nil && l.onErr != nil {
48		l.onErr(err)
49	}
50}
51
52func (l *NCSALogger) log(ent *Entry) error {
53	defer l.mu.Unlock()
54	l.mu.Lock()
55	l.buf = formatEntry(l.buf[:0], ent)
56	_, err := l.w.Write(l.buf)
57	return err
58}
59
60func formatEntry(b []byte, ent *Entry) []byte {
61	const ncsaTime = "02/Jan/2006:15:04:05 -0700"
62	if ent.RemoteIP == "" {
63		b = append(b, '-')
64	} else {
65		b = append(b, ent.RemoteIP...)
66	}
67	b = append(b, " - - ["...)
68	b = ent.ReceivedTime.AppendFormat(b, ncsaTime)
69	b = append(b, "] \""...)
70	b = append(b, ent.RequestMethod...)
71	b = append(b, ' ')
72	b = append(b, ent.RequestURL...)
73	b = append(b, ' ')
74	b = append(b, ent.Proto...)
75	b = append(b, "\" "...)
76	b = strconv.AppendInt(b, int64(ent.Status), 10)
77	b = append(b, ' ')
78	b = strconv.AppendInt(b, int64(ent.ResponseBodySize), 10)
79	b = append(b, ' ')
80	b = strconv.AppendQuote(b, ent.Referer)
81	b = append(b, ' ')
82	b = strconv.AppendQuote(b, ent.UserAgent)
83	b = append(b, '\n')
84	return b
85}
86