1package client
2
3import (
4	"bytes"
5	"fmt"
6	"io"
7	"io/ioutil"
8	"net/http/httputil"
9
10	"github.com/aws/aws-sdk-go/aws"
11	"github.com/aws/aws-sdk-go/aws/request"
12)
13
14const logReqMsg = `DEBUG: Request %s/%s Details:
15---[ REQUEST POST-SIGN ]-----------------------------
16%s
17-----------------------------------------------------`
18
19const logReqErrMsg = `DEBUG ERROR: Request %s/%s:
20---[ REQUEST DUMP ERROR ]-----------------------------
21%s
22------------------------------------------------------`
23
24type logWriter struct {
25	// Logger is what we will use to log the payload of a response.
26	Logger aws.Logger
27	// buf stores the contents of what has been read
28	buf *bytes.Buffer
29}
30
31func (logger *logWriter) Write(b []byte) (int, error) {
32	return logger.buf.Write(b)
33}
34
35type teeReaderCloser struct {
36	// io.Reader will be a tee reader that is used during logging.
37	// This structure will read from a body and write the contents to a logger.
38	io.Reader
39	// Source is used just to close when we are done reading.
40	Source io.ReadCloser
41}
42
43func (reader *teeReaderCloser) Close() error {
44	return reader.Source.Close()
45}
46
47// LogHTTPRequestHandler is a SDK request handler to log the HTTP request sent
48// to a service. Will include the HTTP request body if the LogLevel of the
49// request matches LogDebugWithHTTPBody.
50var LogHTTPRequestHandler = request.NamedHandler{
51	Name: "awssdk.client.LogRequest",
52	Fn:   logRequest,
53}
54
55func logRequest(r *request.Request) {
56	logBody := r.Config.LogLevel.Matches(aws.LogDebugWithHTTPBody)
57	bodySeekable := aws.IsReaderSeekable(r.Body)
58
59	b, err := httputil.DumpRequestOut(r.HTTPRequest, logBody)
60	if err != nil {
61		r.Config.Logger.Log(fmt.Sprintf(logReqErrMsg,
62			r.ClientInfo.ServiceName, r.Operation.Name, err))
63		return
64	}
65
66	if logBody {
67		if !bodySeekable {
68			r.SetReaderBody(aws.ReadSeekCloser(r.HTTPRequest.Body))
69		}
70		// Reset the request body because dumpRequest will re-wrap the r.HTTPRequest's
71		// Body as a NoOpCloser and will not be reset after read by the HTTP
72		// client reader.
73		r.ResetBody()
74	}
75
76	r.Config.Logger.Log(fmt.Sprintf(logReqMsg,
77		r.ClientInfo.ServiceName, r.Operation.Name, string(b)))
78}
79
80// LogHTTPRequestHeaderHandler is a SDK request handler to log the HTTP request sent
81// to a service. Will only log the HTTP request's headers. The request payload
82// will not be read.
83var LogHTTPRequestHeaderHandler = request.NamedHandler{
84	Name: "awssdk.client.LogRequestHeader",
85	Fn:   logRequestHeader,
86}
87
88func logRequestHeader(r *request.Request) {
89	b, err := httputil.DumpRequestOut(r.HTTPRequest, false)
90	if err != nil {
91		r.Config.Logger.Log(fmt.Sprintf(logReqErrMsg,
92			r.ClientInfo.ServiceName, r.Operation.Name, err))
93		return
94	}
95
96	r.Config.Logger.Log(fmt.Sprintf(logReqMsg,
97		r.ClientInfo.ServiceName, r.Operation.Name, string(b)))
98}
99
100const logRespMsg = `DEBUG: Response %s/%s Details:
101---[ RESPONSE ]--------------------------------------
102%s
103-----------------------------------------------------`
104
105const logRespErrMsg = `DEBUG ERROR: Response %s/%s:
106---[ RESPONSE DUMP ERROR ]-----------------------------
107%s
108-----------------------------------------------------`
109
110// LogHTTPResponseHandler is a SDK request handler to log the HTTP response
111// received from a service. Will include the HTTP response body if the LogLevel
112// of the request matches LogDebugWithHTTPBody.
113var LogHTTPResponseHandler = request.NamedHandler{
114	Name: "awssdk.client.LogResponse",
115	Fn:   logResponse,
116}
117
118func logResponse(r *request.Request) {
119	lw := &logWriter{r.Config.Logger, bytes.NewBuffer(nil)}
120
121	if r.HTTPResponse == nil {
122		lw.Logger.Log(fmt.Sprintf(logRespErrMsg,
123			r.ClientInfo.ServiceName, r.Operation.Name, "request's HTTPResponse is nil"))
124		return
125	}
126
127	logBody := r.Config.LogLevel.Matches(aws.LogDebugWithHTTPBody)
128	if logBody {
129		r.HTTPResponse.Body = &teeReaderCloser{
130			Reader: io.TeeReader(r.HTTPResponse.Body, lw),
131			Source: r.HTTPResponse.Body,
132		}
133	}
134
135	handlerFn := func(req *request.Request) {
136		b, err := httputil.DumpResponse(req.HTTPResponse, false)
137		if err != nil {
138			lw.Logger.Log(fmt.Sprintf(logRespErrMsg,
139				req.ClientInfo.ServiceName, req.Operation.Name, err))
140			return
141		}
142
143		lw.Logger.Log(fmt.Sprintf(logRespMsg,
144			req.ClientInfo.ServiceName, req.Operation.Name, string(b)))
145
146		if logBody {
147			b, err := ioutil.ReadAll(lw.buf)
148			if err != nil {
149				lw.Logger.Log(fmt.Sprintf(logRespErrMsg,
150					req.ClientInfo.ServiceName, req.Operation.Name, err))
151				return
152			}
153
154			lw.Logger.Log(string(b))
155		}
156	}
157
158	const handlerName = "awsdk.client.LogResponse.ResponseBody"
159
160	r.Handlers.Unmarshal.SetBackNamed(request.NamedHandler{
161		Name: handlerName, Fn: handlerFn,
162	})
163	r.Handlers.UnmarshalError.SetBackNamed(request.NamedHandler{
164		Name: handlerName, Fn: handlerFn,
165	})
166}
167
168// LogHTTPResponseHeaderHandler is a SDK request handler to log the HTTP
169// response received from a service. Will only log the HTTP response's headers.
170// The response payload will not be read.
171var LogHTTPResponseHeaderHandler = request.NamedHandler{
172	Name: "awssdk.client.LogResponseHeader",
173	Fn:   logResponseHeader,
174}
175
176func logResponseHeader(r *request.Request) {
177	if r.Config.Logger == nil {
178		return
179	}
180
181	b, err := httputil.DumpResponse(r.HTTPResponse, false)
182	if err != nil {
183		r.Config.Logger.Log(fmt.Sprintf(logRespErrMsg,
184			r.ClientInfo.ServiceName, r.Operation.Name, err))
185		return
186	}
187
188	r.Config.Logger.Log(fmt.Sprintf(logRespMsg,
189		r.ClientInfo.ServiceName, r.Operation.Name, string(b)))
190}
191