// LogrusLogger is a middleware that will log each request recieved, along with // some useful information, to the given logger. func LogrusLogger(logger *logrus.Logger, h http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { start := time.Now() entry := logger.WithFields(logrus.Fields{ "request": r.RequestURI, "method": r.Method, "remote": r.RemoteAddr, }) if id := r.Header.Get(RequestIDKey); id != "" { entry = entry.WithField("request_id", id) } // Wrap the writer so we can track data information. neww := WrapWriter(w) // Dispatch to the underlying handler. entry.Info("started handling request") h.ServeHTTP(neww, r) // Log final information. entry.WithFields(logrus.Fields{ "bytes_written": neww.BytesWritten(), "status": neww.Status(), "text_status": http.StatusText(neww.Status()), "took": time.Since(start), }).Info("completed handling request") } return http.HandlerFunc(fn) }
func userDefinedCustomResourceForwarder(customResource *customResourceInfo, event *json.RawMessage, context *LambdaContext, w http.ResponseWriter, logger *logrus.Logger) { var rawProps map[string]interface{} json.Unmarshal([]byte(*event), &rawProps) var lambdaEvent cloudformationresources.CloudFormationLambdaEvent jsonErr := json.Unmarshal([]byte(*event), &lambdaEvent) if jsonErr != nil { logger.WithFields(logrus.Fields{ "RawEvent": rawProps, "UnmarshalError": jsonErr, }).Warn("Raw event data") http.Error(w, jsonErr.Error(), http.StatusInternalServerError) } logger.WithFields(logrus.Fields{ "LambdaEvent": lambdaEvent, }).Debug("CloudFormation user resource lambda event") // Create the new request and send it off customResourceRequest := &cloudformationresources.UserFuncResourceRequest{} customResourceRequest.LambdaHandler = func(requestType string, stackID string, properties map[string]interface{}, logger *logrus.Logger) (map[string]interface{}, error) { // Descend to get the "UserProperties" field iff defined by the customResource var userProperties map[string]interface{} if _, exists := lambdaEvent.ResourceProperties["UserProperties"]; exists { childProps, ok := lambdaEvent.ResourceProperties["UserProperties"].(map[string]interface{}) if !ok { return nil, fmt.Errorf("Failed to extract UserProperties from payload") } userProperties = childProps } return customResource.userFunction(requestType, stackID, userProperties, logger) } customResourceRequest.RequestType = lambdaEvent.RequestType customResourceRequest.ResponseURL = lambdaEvent.ResponseURL customResourceRequest.StackID = lambdaEvent.StackID customResourceRequest.RequestID = lambdaEvent.RequestID customResourceRequest.LogicalResourceID = lambdaEvent.LogicalResourceID customResourceRequest.PhysicalResourceID = lambdaEvent.PhysicalResourceID customResourceRequest.LogGroupName = context.LogGroupName customResourceRequest.LogStreamName = context.LogStreamName customResourceRequest.ResourceProperties = lambdaEvent.ResourceProperties if "" == customResourceRequest.PhysicalResourceID { customResourceRequest.PhysicalResourceID = fmt.Sprintf("LogStreamName: %s", context.LogStreamName) } requestErr := cloudformationresources.Run(customResourceRequest, logger) if requestErr != nil { http.Error(w, requestErr.Error(), http.StatusInternalServerError) } else { fmt.Fprint(w, "CustomResource handled: "+lambdaEvent.LogicalResourceID) } }
// Main is the top of the pile. Start here. func Main(log *logrus.Logger) { opts := NewOptions() if opts.Debug { log.Level = logrus.DebugLevel } if opts.FileStorePrefix == "" { opts.FileStorePrefix = "tmp" } server, err := NewServer(opts, log, nil) if err != nil { log.Fatal(err) } port := os.Getenv("PORT") if port == "" { port = "9839" } addr := fmt.Sprintf(":%s", port) log.WithFields(logrus.Fields{ "addr": addr, }).Info("artifacts-service listening") server.Run(addr) }
// Run manages invoking a user supplied function to perform // the CloudFormation resource operation. Clients do not need // to implement anything cloudformationresource related. func Run(request *UserFuncResourceRequest, logger *logrus.Logger) error { logger.WithFields(logrus.Fields{ "Name": aws.SDKName, "Version": aws.SDKVersion, }).Debug("CloudFormation CustomResource AWS SDK info") operationOutputs, operationError := request.LambdaHandler(request.RequestType, request.StackID, request.ResourceProperties, logger) // Notify CloudFormation of the result if "" != request.ResponseURL { sendErr := sendCloudFormationResponse(&request.AbstractCustomResourceRequest, operationOutputs, operationError, logger) if nil != sendErr { logger.WithFields(logrus.Fields{ "Error": sendErr.Error(), }).Info("Failed to notify CloudFormation of result.") } else { // If the cloudformation notification was complete, then this // execution functioned properly and we can clear the Error operationError = nil } } return operationError }
// Delete the provided serviceName. Failing to delete a non-existent // service is not considered an error. Note that the delete does func Delete(serviceName string, logger *logrus.Logger) error { session := awsSession(logger) awsCloudFormation := cloudformation.New(session) exists, err := stackExists(serviceName, awsCloudFormation, logger) if nil != err { return err } logger.WithFields(logrus.Fields{ "Exists": exists, "Name": serviceName, }).Info("Stack existence check") if exists { params := &cloudformation.DeleteStackInput{ StackName: aws.String(serviceName), } resp, err := awsCloudFormation.DeleteStack(params) if nil != resp { logger.WithFields(logrus.Fields{ "Response": resp, }).Info("Delete request submitted") } return err } logger.Info("Stack does not exist") return nil }
func s3LambdaProcessor(event *json.RawMessage, context *LambdaContext, w http.ResponseWriter, logger *logrus.Logger) { logger.WithFields(logrus.Fields{ "RequestID": context.AWSRequestID, }).Info("S3Event") logger.Info("Event data: ", string(*event)) }
func annotateDiscoveryInfo(template *gocf.Template, logger *logrus.Logger) *gocf.Template { for eachResourceID, eachResource := range template.Resources { // Only apply this to lambda functions if eachResource.Properties.CfnResourceType() == "AWS::Lambda::Function" { // Update the metdata with a reference to the output of each // depended on item... for _, eachDependsKey := range eachResource.DependsOn { dependencyOutputs, _ := outputsForResource(template, eachDependsKey, logger) if nil != dependencyOutputs && len(dependencyOutputs) != 0 { logger.WithFields(logrus.Fields{ "Resource": eachDependsKey, "DependsOn": eachResource.DependsOn, "Outputs": dependencyOutputs, }).Debug("Resource metadata") safeMetadataInsert(eachResource, eachDependsKey, dependencyOutputs) } } // Also include standard AWS outputs at a resource level if a lambda // needs to self-discover other resources safeMetadataInsert(eachResource, TagLogicalResourceID, gocf.String(eachResourceID)) safeMetadataInsert(eachResource, TagStackRegion, gocf.Ref("AWS::Region")) safeMetadataInsert(eachResource, TagStackID, gocf.Ref("AWS::StackId")) safeMetadataInsert(eachResource, TagStackName, gocf.Ref("AWS::StackName")) } } return template }
// StackExists returns whether the given stackName or stackID currently exists func StackExists(stackNameOrID string, awsSession *session.Session, logger *logrus.Logger) (bool, error) { cf := cloudformation.New(awsSession) describeStacksInput := &cloudformation.DescribeStacksInput{ StackName: aws.String(stackNameOrID), } describeStacksOutput, err := cf.DescribeStacks(describeStacksInput) logger.WithFields(logrus.Fields{ "DescribeStackOutput": describeStacksOutput, }).Debug("DescribeStackOutput results") exists := false if err != nil { logger.WithFields(logrus.Fields{ "DescribeStackOutputError": err, }).Debug("DescribeStackOutput") // If the stack doesn't exist, then no worries if strings.Contains(err.Error(), "does not exist") { exists = false } else { return false, err } } else { exists = true } return exists, nil }
// LogRPCWithFields will feed any request context into a logrus Entry. func LogRPCWithFields(log *logrus.Logger, ctx context.Context) *logrus.Entry { md, ok := metadata.FromContext(ctx) if !ok { return logrus.NewEntry(log) } return log.WithFields(MetadataToFields(md)) }
func validateSpartaPreconditions(lambdaAWSInfos []*LambdaAWSInfo, logger *logrus.Logger) error { var errorText []string collisionMemo := make(map[string]string, 0) // 1 - check for duplicate golang function references. for _, eachLambda := range lambdaAWSInfos { testName := eachLambda.lambdaFnName if _, exists := collisionMemo[testName]; !exists { collisionMemo[testName] = testName // We'll always find our own lambda duplicateCount := 0 for _, eachCheckLambda := range lambdaAWSInfos { if testName == eachCheckLambda.lambdaFnName { duplicateCount++ } } // We'll always find our own lambda if duplicateCount > 1 { logger.WithFields(logrus.Fields{ "CollisionCount": duplicateCount, "Name": testName, }).Error("Detected Sparta lambda function associated with multiple LambdaAWSInfo structs") errorText = append(errorText, fmt.Sprintf("Multiple definitions of lambda: %s", testName)) } } } if len(errorText) != 0 { return errors.New(strings.Join(errorText[:], "\n")) } return nil }
func LogError(r *http.Request, err error, info string, logger *log.Logger) { logger.WithFields(log.Fields{ "error": err.Error(), "method": r.Method, "url": r.URL.String(), }).Error(info) }
// NewWithNameAndLogger returns a new middleware handler with the specified name // and logger func NewWithNameAndLogger(name string, l *logrus.Logger) echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c *echo.Context) error { start := time.Now() entry := l.WithFields(logrus.Fields{ "request": c.Request().RequestURI, "method": c.Request().Method, "remote": c.Request().RemoteAddr, }) if reqID := c.Request().Header.Get("X-Request-Id"); reqID != "" { entry = entry.WithField("request_id", reqID) } entry.Info("started handling request") if err := next(c); err != nil { c.Error(err) } latency := time.Since(start) entry.WithFields(logrus.Fields{ "status": c.Response().Status(), "text_status": http.StatusText(c.Response().Status()), "took": latency, fmt.Sprintf("measure#%s.latency", name): latency.Nanoseconds(), }).Info("completed handling request") return nil } } }
// NewSessionWithLevel returns an AWS Session (https://github.com/aws/aws-sdk-go/wiki/Getting-Started-Configuration) // object that attaches a debug level handler to all AWS requests from services // sharing the session value. func NewSessionWithLevel(level aws.LogLevelType, logger *logrus.Logger) *session.Session { awsConfig := &aws.Config{ CredentialsChainVerboseErrors: aws.Bool(true), } // Log AWS calls if needed switch logger.Level { case logrus.DebugLevel: awsConfig.LogLevel = aws.LogLevel(level) } awsConfig.Logger = &logrusProxy{logger} sess := session.New(awsConfig) sess.Handlers.Send.PushFront(func(r *request.Request) { logger.WithFields(logrus.Fields{ "Service": r.ClientInfo.ServiceName, "Operation": r.Operation.Name, "Method": r.Operation.HTTPMethod, "Path": r.Operation.HTTPPath, "Payload": r.Params, }).Debug("AWS Request") }) logger.WithFields(logrus.Fields{ "Name": aws.SDKName, "Version": aws.SDKVersion, }).Debug("AWS SDK Info") return sess }
// Execute creates an HTTP listener to dispatch execution. Typically // called via Main() via command line arguments. func Execute(lambdaAWSInfos []*LambdaAWSInfo, port int, parentProcessPID int, logger *logrus.Logger) error { if port <= 0 { port = defaultHTTPPort } server := &http.Server{ Addr: fmt.Sprintf(":%d", port), Handler: NewLambdaHTTPHandler(lambdaAWSInfos, logger), ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, } if 0 != parentProcessPID { logger.Debug("Sending SIGUSR2 to parent process: ", parentProcessPID) syscall.Kill(parentProcessPID, syscall.SIGUSR2) } logger.WithFields(logrus.Fields{ "URL": fmt.Sprintf("http://localhost:%d", port), }).Info("Starting Sparta server") err := server.ListenAndServe() if err != nil { logger.WithFields(logrus.Fields{ "Error": err.Error(), }).Error("Failed to launch server") return err } return nil }
// Ginrus returns a gin.HandlerFunc (middleware) that logs requests using logrus. // // Requests with errors are logged using logrus.Error(). // Requests without errors are logged using logrus.Info(). // // It receives: // 1. A time package format string (e.g. time.RFC3339). // 2. A boolean stating whether to use UTC time zone or local. func Ginrus(logger *logrus.Logger, timeFormat string, utc bool) gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() // some evil middlewares modify this values path := c.Request.URL.Path c.Next() end := time.Now() latency := end.Sub(start) if utc { end = end.UTC() } entry := logger.WithFields(logrus.Fields{ "status": c.Writer.Status(), "method": c.Request.Method, "path": path, "ip": c.ClientIP(), "latency": latency, "user-agent": c.Request.UserAgent(), "time": end.Format(timeFormat), }) if len(c.Errors) > 0 { // Append error field if this is an erroneous request. entry.Error(c.Errors.String()) } else { entry.Info() } } }
// Explore supports interactive command line invocation of the previously // provisioned Sparta service func Explore(lambdaAWSInfos []*LambdaAWSInfo, port int, logger *logrus.Logger) error { if 0 == port { port = 9999 } urlHost := fmt.Sprintf("http://*****:*****@testEvent.json %s", functionURL) } } logger.Info("Functions can be invoked via application/json over POST") logger.Info(msgText) logger.Info("Where @testEvent.json is a local file with top level `context` and `event` properties:") logger.Info("\t{context: {}, event: {}}") // Start up the localhost server and publish the info return Execute(lambdaAWSInfos, port, 0, logger) }
//////////////////////////////////////////////////////////////////////////////// // S3 handler // func echoS3Event(event *json.RawMessage, context *sparta.LambdaContext, w http.ResponseWriter, logger *logrus.Logger) { logger.WithFields(logrus.Fields{ "RequestID": context.AWSRequestID, "Event": string(*event), }).Info("Request received") fmt.Fprintf(w, string(*event)) }
func handleConnection(conn net.Conn, jobd *jobserver.JobServer, cbor *codec.CborHandle, reqLogger *logrus.Logger) { defer conn.Close() var reqLog, errLog *logrus.Entry fields := logrus.Fields{ "remote": conn.RemoteAddr(), } errLog = logrus.WithFields(fields) if reqLogger != nil { reqLog = reqLogger.WithFields(fields) } jobdv := reflect.ValueOf(jobd) reader := bufio.NewReader(conn) decoder := codec.NewDecoder(reader, cbor) writer := bufio.NewWriter(conn) encoder := codec.NewEncoder(writer, cbor) for { var request cborrpc.Request err := decoder.Decode(&request) if err == io.EOF { if reqLog != nil { reqLog.Debug("Connection closed") } return } else if err != nil { errLog.WithError(err).Error("Error reading message") return } if reqLog != nil { reqLog.WithFields(logrus.Fields{ "id": request.ID, "method": request.Method, }).Debug("Request") } response := doRequest(jobdv, request) if reqLog != nil { entry := reqLog.WithField("id", response.ID) if response.Error != "" { entry = entry.WithField("error", response.Error) } entry.Debug("Response") } err = encoder.Encode(response) if err != nil { errLog.WithError(err).Error("Error encoding response") return } err = writer.Flush() if err != nil { errLog.WithError(err).Error("Error writing response") return } } }
// Return a string representation of a JS function call that can be exposed // to AWS Lambda func createNewNodeJSProxyEntry(lambdaInfo *LambdaAWSInfo, logger *logrus.Logger) string { logger.WithFields(logrus.Fields{ "Handler": lambdaInfo.jsHandlerName(), }).Info("Creating NodeJS proxy entry") primaryEntry := fmt.Sprintf("exports[\"%s\"] = createForwarder(\"/%s\");\n", lambdaInfo.jsHandlerName(), lambdaInfo.lambdaFnName) return primaryEntry }
func logFilesize(message string, filePath string, logger *logrus.Logger) { // Binary size stat, err := os.Stat(filePath) if err == nil { logger.WithFields(logrus.Fields{ "KB": stat.Size() / 1024, "MB": stat.Size() / (1024 * 1024), }).Info(message) } }
func newCloudFormationResource(resourceType string, logger *logrus.Logger) (gocf.ResourceProperties, error) { resProps := gocf.NewResourceByType(resourceType) if nil == resProps { logger.WithFields(logrus.Fields{ "Type": resourceType, }).Fatal("Failed to create CloudFormation CustomResource!") return nil, fmt.Errorf("Unsupported CustomResourceType: %s", resourceType) } return resProps, nil }
func cloudWatchEventProcessor(event *json.RawMessage, context *LambdaContext, w http.ResponseWriter, logger *logrus.Logger) { logger.WithFields(logrus.Fields{ "RequestID": context.AWSRequestID, }).Info("Request received") logger.Info("CloudWatch Event data: ", string(*event)) }
func withLogging(l *log.Logger, h http.Handler) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { startTime := time.Now() h.ServeHTTP(w, r) l.WithFields(log.Fields{ "method": r.Method, "path": r.URL.Path, "duration": time.Since(startTime), }).Info() } }
func echoAPIGatewayEvent(event *json.RawMessage, context *LambdaContext, w http.ResponseWriter, logger *logrus.Logger) { logger.WithFields(logrus.Fields{ "RequestID": context.AWSRequestID, "Event": string(*event), }).Info("Request received") fmt.Fprintf(w, "Hello World!") }
func (command SNSLambdaEventSourceResource) updateRegistration(isTargetActive bool, session *session.Session, logger *logrus.Logger) (map[string]interface{}, error) { // Get the current subscriptions... snsSvc := sns.New(session) snsInput := &sns.ListSubscriptionsByTopicInput{ TopicArn: aws.String(command.SNSTopicArn.Literal), } listSubscriptions, listSubscriptionsErr := snsSvc.ListSubscriptionsByTopic(snsInput) if nil != listSubscriptionsErr { return nil, listSubscriptionsErr } var lambdaSubscriptionArn string for _, eachSubscription := range listSubscriptions.Subscriptions { if *eachSubscription.Protocol == "lambda" && *eachSubscription.Endpoint == command.LambdaTargetArn.Literal { if "" != lambdaSubscriptionArn { return nil, fmt.Errorf("Multiple SNS %s registrations found for lambda: %s", *snsInput.TopicArn, command.LambdaTargetArn.Literal) } lambdaSubscriptionArn = *eachSubscription.SubscriptionArn } } // Just log it... logger.WithFields(logrus.Fields{ "SNSTopicArn": command.SNSTopicArn, "LambdaArn": command.LambdaTargetArn, "ExistingSubscriptionArn": lambdaSubscriptionArn, }).Info("Current SNS subscription status") var opErr error if isTargetActive && "" == lambdaSubscriptionArn { subscribeInput := &sns.SubscribeInput{ Protocol: aws.String("lambda"), TopicArn: aws.String(command.SNSTopicArn.Literal), Endpoint: aws.String(command.LambdaTargetArn.Literal), } _, opErr = snsSvc.Subscribe(subscribeInput) } else if !isTargetActive && "" != lambdaSubscriptionArn { unsubscribeInput := &sns.UnsubscribeInput{ SubscriptionArn: aws.String(lambdaSubscriptionArn), } _, opErr = snsSvc.Unsubscribe(unsubscribeInput) } else { // Just log it... logger.WithFields(logrus.Fields{ "Command": command, }).Info("No SNS operation required") } return nil, opErr }
func (command ZipToS3BucketResource) delete(session *session.Session, logger *logrus.Logger) (map[string]interface{}, error) { // Remove all objects from the bucket totalItemsDeleted := 0 svc := s3.New(session) deleteItemsHandler := func(objectOutputs *s3.ListObjectsOutput, lastPage bool) bool { params := &s3.DeleteObjectsInput{ Bucket: aws.String(command.DestBucket.Literal), Delete: &s3.Delete{ // Required Objects: []*s3.ObjectIdentifier{}, Quiet: aws.Bool(true), }, } for _, eachObject := range objectOutputs.Contents { totalItemsDeleted++ params.Delete.Objects = append(params.Delete.Objects, &s3.ObjectIdentifier{ Key: eachObject.Key, }) } _, deleteResultErr := svc.DeleteObjects(params) return nil == deleteResultErr } // Walk the bucket and cleanup... params := &s3.ListObjectsInput{ Bucket: aws.String(command.DestBucket.Literal), MaxKeys: aws.Int64(1000), } err := svc.ListObjectsPages(params, deleteItemsHandler) if nil != err { return nil, err } // Cleanup the Manifest iff defined var deleteErr error if nil != command.Manifest { name := command.ManifestName if "" == name { name = DefaultManifestName } manifestDeleteParams := &s3.DeleteObjectInput{ Bucket: aws.String(command.DestBucket.Literal), Key: aws.String(name), } _, deleteErr = svc.DeleteObject(manifestDeleteParams) logger.WithFields(logrus.Fields{ "TotalDeletedCount": totalItemsDeleted, "S3Bucket": command.DestBucket, }).Info("Purged S3 Bucket") } return nil, deleteErr }
// Standard AWS λ function func helloWorld(event *json.RawMessage, context *LambdaContext, w http.ResponseWriter, logger *logrus.Logger) { configuration, _ := Discover() logger.WithFields(logrus.Fields{ "Discovery": configuration, }).Info("Custom resource request") fmt.Fprint(w, "Hello World") }
func runOSCommand(cmd *exec.Cmd, logger *logrus.Logger) error { logger.WithFields(logrus.Fields{ "Arguments": cmd.Args, "Dir": cmd.Dir, "Path": cmd.Path, "Env": cmd.Env, }).Debug("Running Command") outputWriter := logger.Writer() defer outputWriter.Close() cmd.Stdout = outputWriter cmd.Stderr = outputWriter return cmd.Run() }
// Returns an AWS Session (https://github.com/aws/aws-sdk-go/wiki/Getting-Started-Configuration) // object that attaches a debug level handler to all AWS requests from services // sharing the session value. func awsSession(logger *logrus.Logger) *session.Session { sess := session.New() sess.Handlers.Send.PushFront(func(r *request.Request) { logger.WithFields(logrus.Fields{ "Service": r.ClientInfo.ServiceName, "Operation": r.Operation.Name, "Method": r.Operation.HTTPMethod, "Path": r.Operation.HTTPPath, "Payload": r.Params, }).Debug("AWS Request") }) return sess }
//////////////////////////////////////////////////////////////////////////////// // CloudWatchEvent handler // func echoCloudWatchEvent(event *json.RawMessage, context *sparta.LambdaContext, w http.ResponseWriter, logger *logrus.Logger) { logger.WithFields(logrus.Fields{ "RequestID": context.AWSRequestID, }).Info("Request received") config, _ := sparta.Discover() logger.WithFields(logrus.Fields{ "RequestID": context.AWSRequestID, "Event": string(*event), "Configuration": config, }).Info("Request received") fmt.Fprintf(w, "Hello World!") }