diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2198dac --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright © 2019 Björn Ahl + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/cmd/cat.go b/cmd/cat.go new file mode 100644 index 0000000..0f22ef7 --- /dev/null +++ b/cmd/cat.go @@ -0,0 +1,98 @@ +// Copyright © 2019 NAME HERE +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "bytes" + "fmt" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/s3" + "github.com/dbgeek/elblogcat/logcat" + "github.com/dbgeek/elblogcat/logworker" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// catCmd represents the cat command +var catCmd = &cobra.Command{ + Use: "cat", + Short: "cat accesslog from s3", + Long: `Download the accesslog and cat it + +possible user these filter +* client-ip +* elb-status-code +* target-status-code +* http-method +`, + Run: func(cmd *cobra.Command, args []string) { + awsConfiguration := logworker.AWSconfiguration{Region: "eu-west-1"} + configuration := logworker.NewConfiguration() + + accessLogFilter := logworker.NewAccessLogFilter() + client := logworker.NewLogWorker( + &awsConfiguration, + &configuration, + &accessLogFilter, + ) + + for _, v := range client.List() { + buff := &aws.WriteAtBuffer{} + key := fmt.Sprintf("%s%s%s", configuration.Prefix, accessLogFilter.AccesslogPath(), v) + _, err := client.S3Downloader.Download(buff, &s3.GetObjectInput{ + Bucket: aws.String(viper.GetString("s3-bucket")), + Key: aws.String(key), + }) + if err != nil { + logworker.Logger.Fatalf("Failed to Download key: %v from s3. Got error: %v", + key, + err) + } + + c := logcat.NewRowFilter() + b := bytes.NewBuffer(buff.Bytes()) + a := logcat.Accesslog{ + Content: b, + RowFilter: c, + } + for _, row := range a.Cat() { + fmt.Println(row) + } + } + }, +} + +func init() { + rootCmd.AddCommand(catCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // catCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + //catCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + catCmd.PersistentFlags().StringP("client-ip", "", ".*", "") + viper.BindPFlag("client-ip", catCmd.PersistentFlags().Lookup("client-ip")) + catCmd.PersistentFlags().StringP("elb-status-code", "", ".*", "") + viper.BindPFlag("elb-status-code", catCmd.PersistentFlags().Lookup("elb-status-code")) + catCmd.PersistentFlags().StringP("target-status-code", "", ".*", "") + viper.BindPFlag("target-status-code", catCmd.PersistentFlags().Lookup("target-status-code")) + catCmd.PersistentFlags().StringP("http-method", "", ".*", "") + viper.BindPFlag("http-method", catCmd.PersistentFlags().Lookup("http-method")) +} diff --git a/cmd/list.go b/cmd/list.go new file mode 100644 index 0000000..d70bef5 --- /dev/null +++ b/cmd/list.go @@ -0,0 +1,63 @@ +// Copyright © 2019 NAME HERE +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "fmt" + + "github.com/dbgeek/elblogcat/logworker" + "github.com/spf13/cobra" +) + +// listCmd represents the list command +var listCmd = &cobra.Command{ + Use: "list", + Short: "Print accesslogs that exists", + Long: `Print all accesslogs that exists and possible to filter by + + * region + * start time + * loadbalancer id +`, + Run: func(cmd *cobra.Command, args []string) { + awsConfiguration := logworker.AWSconfiguration{Region: "eu-west-1"} + configuration := logworker.NewConfiguration() + accessLogFilter := logworker.NewAccessLogFilter() + client := logworker.NewLogWorker( + &awsConfiguration, + &configuration, + &accessLogFilter, + ) + + for _, v := range client.List() { + fmt.Println(v) + } + + }, +} + +func init() { + rootCmd.AddCommand(listCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // listCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // listCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..70fe461 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,115 @@ +// Copyright © 2019 Björn Ahl +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package cmd + +import ( + "fmt" + "os" + + homedir "github.com/mitchellh/go-homedir" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var cfgFile string + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "elblogcat", + Short: "List & cat aws lb acesses", + Long: `List & cat aws alb/elb accesslog that are stored in s3. + +Filter output is possible by: +* timerange for one day +* loadbalancer id +* loadbalancer ip-address +* accesslog unique string +`, + // Uncomment the following line if your bare application + // has an action associated with it: + // Run: func(cmd *cobra.Command, args []string) { }, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +func init() { + cobra.OnInitialize(initConfig) + + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.elblogcat.yaml)") + rootCmd.PersistentFlags().StringP("aws-account-id", "a", "", "The AWS account ID of the owner.") + viper.BindPFlag("aws-account-id", rootCmd.PersistentFlags().Lookup("aws-account-id")) + rootCmd.PersistentFlags().StringP("region", "r", "", "The region for your load balancer and S3 bucket.") + viper.BindPFlag("region", rootCmd.PersistentFlags().Lookup("region")) + rootCmd.PersistentFlags().StringP("load-balancer-id", "l", ".*", "The resource ID of the load balancer. If the resource ID contains any forward slashes (/), they are replaced with periods (.).") + viper.BindPFlag("load-balancer-id", rootCmd.PersistentFlags().Lookup("load-balancer-id")) + rootCmd.PersistentFlags().StringP("ip-address", "i", ".*", "The IP address of the load balancer node that handled the request. For an internal load balancer, this is a private IP address.") + viper.BindPFlag("ip-address", rootCmd.PersistentFlags().Lookup("ip-address")) + rootCmd.PersistentFlags().StringP("random-string", "s", ".*", "A system-generated random string.") + viper.BindPFlag("random-string", rootCmd.PersistentFlags().Lookup("random-string")) + rootCmd.PersistentFlags().StringP("s3-bucket", "b", ".*", "The name of the S3 bucket.") + viper.BindPFlag("s3-bucket", rootCmd.PersistentFlags().Lookup("s3-bucket")) + rootCmd.PersistentFlags().StringP("s3-prefix", "p", ".*", "The prefix (logical hierarchy) in the bucket. If you don't specify a prefix, the logs are placed at the root level of the bucket.") + viper.BindPFlag("s3-prefix", rootCmd.PersistentFlags().Lookup("s3-prefix")) + rootCmd.PersistentFlags().StringP("start-time", "", ".*", "") + viper.BindPFlag("start-time", rootCmd.PersistentFlags().Lookup("start-time")) + rootCmd.PersistentFlags().StringP("end-time", "", ".*", "") + viper.BindPFlag("end-time", rootCmd.PersistentFlags().Lookup("end-time")) + + // Cobra also supports local flags, which will only run + // when this action is called directly. + rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} + +// initConfig reads in config file and ENV variables if set. +func initConfig() { + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + // Find home directory. + home, err := homedir.Dir() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + // Search config in home directory with name ".elblogcat" (without extension). + viper.AddConfigPath(home) + viper.SetConfigName(".elblogcat") + } + + viper.AutomaticEnv() // read in environment variables that match + + // If a config file is found, read it in. + if err := viper.ReadInConfig(); err == nil { + fmt.Println("Using config file:", viper.ConfigFileUsed()) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..4a1b0c2 --- /dev/null +++ b/go.mod @@ -0,0 +1,17 @@ +module github.com/dbgeek/elblogcat + +require ( + github.com/BurntSushi/toml v0.3.1 // indirect + github.com/aws/aws-sdk-go v1.17.4 + github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/kr/pretty v0.1.0 // indirect + github.com/mitchellh/go-homedir v1.1.0 + github.com/sirupsen/logrus v1.3.0 + github.com/spf13/cobra v0.0.3 + github.com/spf13/viper v1.3.1 + github.com/stretchr/testify v1.3.0 // indirect + golang.org/x/crypto v0.0.0-20190222235706-ffb98f73852f // indirect + golang.org/x/net v0.0.0-20190213061140-3a22650c66bd // indirect + golang.org/x/sys v0.0.0-20190222171317-cd391775e71e // indirect + gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..8ebeb7d --- /dev/null +++ b/go.sum @@ -0,0 +1,77 @@ +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/aws/aws-sdk-go v1.17.4 h1:L2KFocQhg48kIzEAV98SnSz3nmIZ3UDFP+vU647KO3c= +github.com/aws/aws-sdk-go v1.17.4/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.3.0 h1:hI/7Q+DtNZ2kINb6qt/lS+IyXnHQe9e90POfeewL/ME= +github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.1 h1:5+8j8FTpnFV4nEImW/ofkzEt8VoOiLXxdYIDsB73T38= +github.com/spf13/viper v1.3.1/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190222235706-ffb98f73852f h1:qWFY9ZxP3tfI37wYIs/MnIAqK0vlXp1xnYEa5HxFSSY= +golang.org/x/crypto v0.0.0-20190222235706-ffb98f73852f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd h1:HuTn7WObtcDo9uEEU7rEqL0jYthdXAmZ6PP+meazmaU= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a h1:1n5lsVfiQW3yfsRGu98756EH1YthsFqr/5mxHduZW2A= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222171317-cd391775e71e h1:oF7qaQxUH6KzFdKN4ww7NpPdo53SZi4UlcksLrb2y/o= +golang.org/x/sys v0.0.0-20190222171317-cd391775e71e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/logcat/cat.go b/logcat/cat.go new file mode 100644 index 0000000..34423ad --- /dev/null +++ b/logcat/cat.go @@ -0,0 +1,74 @@ +package logcat + +import ( + "bufio" + "bytes" + "compress/gzip" + "fmt" + "regexp" + + "github.com/dbgeek/elblogcat/logworker" + "github.com/spf13/viper" +) + +type ( + Accesslog struct { + Content *bytes.Buffer + RowFilter Filter + } + Filter struct { + ClientIP string + ElbStatusCode string + TargetStatusCode string + HTTPmethod string + } + + entry struct { + row string + } + rowMatch struct { + matchString string + matcher *regexp.Regexp + } +) + +func (a *Accesslog) Cat() []string { + gzReader, err := gzip.NewReader(a.Content) + if err != nil { + logworker.Logger.Fatalf("new gzip reader failed with: %v", err) + } + s := []string{} + scanner := bufio.NewScanner(gzReader) + filter := newRowMatch(a.RowFilter) + for scanner.Scan() { + if filter.matcher.MatchString(scanner.Text()) { + s = append(s, scanner.Text()) + } + } + return s + +} +func newRowMatch(filter Filter) *rowMatch { + r := rowMatch{} + r.matchString = fmt.Sprintf("^(.*) (.*) (.*) (%s:.*) (.*) (.*) (.*) (.*) (%s) (%s) (.*) (.*) (\"%s.*) (.*) (.*) (.*) (.*) (.*) (.*) (.*) (.*) (.*) (.*) (.*) (.*) (.*)$", + filter.ClientIP, + filter.ElbStatusCode, + filter.TargetStatusCode, + filter.HTTPmethod) + + regExp, err := regexp.Compile(r.matchString) + if err != nil { + logworker.Logger.Fatalf("Failed rowmatch compile regexp got error: %v", err) + } + r.matcher = regExp + return &r +} + +func NewRowFilter() Filter { + return Filter{ + ClientIP: viper.GetString("client-ip"), + ElbStatusCode: viper.GetString("elb-status-code"), + TargetStatusCode: viper.GetString("target-status-code"), + HTTPmethod: viper.GetString("http-method"), + } +} diff --git a/logcat/cat_test.go b/logcat/cat_test.go new file mode 100644 index 0000000..947a115 --- /dev/null +++ b/logcat/cat_test.go @@ -0,0 +1,98 @@ +package logcat + +import ( + "bytes" + "compress/gzip" + "testing" +) + +func TestAccessLogFilter(t *testing.T) { + tt := []struct { + name string + testData []byte + clientIP string + ElbStatusCode string + targetStatusCode string + HTTPmethod string + out bool + }{ + { + "no-filter", + []byte(`https 2019-02-02T00:14:07.437021Z elb01 10.222.161.42:32774 10.222.20.10:443 0.000 0.002 0.000 200 200 371 178 "GET https://elb01.prod.com:443/status HTTP/1.1" "Faraday v0.9.2" ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 arn:aws:elasticloadbalancing:eu-west-1:0123456789:targetgroup/prod-tg/8f858d88ba9c836c "Root=1-xxxxxx-yyyyyyyyyyyyyyyyyyyyy" "elb01.prod.com" "arn:aws:acm:eu-west-1:0123456789:certificate/bbbbbbbb-1cbf-4f99-aaaa-cccccccccccc" 0 2019-02-02T00:14:07.435000Z "forward" "-" "-"`), + ".*", + ".*", + ".*", + ".*", + true, + }, + { + "filter-clientIP", + []byte(`https 2019-02-02T00:14:07.437021Z elb01 10.222.161.42:32774 10.222.20.10:443 0.000 0.002 0.000 200 200 371 178 "GET https://elb01.prod.com:443/status HTTP/1.1" "Faraday v0.9.2" ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 arn:aws:elasticloadbalancing:eu-west-1:0123456789:targetgroup/prod-tg/8f858d88ba9c836c "Root=1-xxxxxx-yyyyyyyyyyyyyyyyyyyyy" "elb01.prod.com" "arn:aws:acm:eu-west-1:0123456789:certificate/bbbbbbbb-1cbf-4f99-aaaa-cccccccccccc" 0 2019-02-02T00:14:07.435000Z "forward" "-" "-"`), + "10.222.161.42", + ".*", + ".*", + ".*", + true, + }, + { + "filter-elbstatuscode", + []byte(`https 2019-02-02T00:14:07.437021Z elb01 10.222.161.42:32774 10.222.20.10:443 0.000 0.002 0.000 200 200 371 178 "GET https://elb01.prod.com:443/status HTTP/1.1" "Faraday v0.9.2" ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 arn:aws:elasticloadbalancing:eu-west-1:0123456789:targetgroup/prod-tg/8f858d88ba9c836c "Root=1-xxxxx-yyyyyyyyyyyyyyyyyyyyy" "elb01.prod.com" "arn:aws:acm:eu-west-1:0123456789:certificate/bbbbbbbb-1cbf-4f99-aaaa-cccccccccccc" 0 2019-02-02T00:14:07.435000Z "forward" "-" "-"`), + ".*", + "200", + ".*", + ".*", + true, + }, + { + "filter-elbstatuscode-startwith-2", + []byte(`https 2019-02-02T00:14:07.437021Z elb01 10.222.161.42:32774 10.222.20.10:443 0.000 0.002 0.000 250 200 371 178 "GET https://elb01.prod.com:443/status HTTP/1.1" "Faraday v0.9.2" ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 arn:aws:elasticloadbalancing:eu-west-1:0123456789:targetgroup/prod-tg/8f858d88ba9c836c "Root=1-xxxxx-yyyyyyyyyyyyyyyyyyyyy" "elb01.prod.com" "arn:aws:acm:eu-west-1:0123456789:certificate/bbbbbbbb-1cbf-4f99-aaaa-cccccccccccc" 0 2019-02-02T00:14:07.435000Z "forward" "-" "-"`), + ".*", + "2.*", + ".*", + ".*", + true, + }, + { + "filter-targetstatuscode", + []byte(`https 2019-02-02T00:14:07.437021Z elb01 10.222.161.42:32774 10.222.20.10:443 0.000 0.002 0.000 200 200 371 178 "GET https://elb01.prod.com:443/status HTTP/1.1" "Faraday v0.9.2" ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 arn:aws:elasticloadbalancing:eu-west-1:0123456789:targetgroup/prod-tg/8f858d88ba9c836c "Root=1-xxxxx-yyyyyyyyyyyyyyyyyyyyy" "elb01.prod.com" "arn:aws:acm:eu-west-1:0123456789:certificate/bbbbbbbb-1cbf-4f99-aaaa-cccccccccccc" 0 2019-02-02T00:14:07.435000Z "forward" "-" "-"`), + ".*", + ".*", + "200", + ".*", + true, + }, + { + "filter-elbstatuscode", + []byte(`https 2019-02-02T00:14:07.437021Z elb01 10.225.161.42:32774 10.225.24.10:443 0.000 0.002 0.000 200 200 371 178 "GET https://elb01.prod.com:443/status HTTP/1.1" "Faraday v0.9.2" ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 arn:aws:elasticloadbalancing:eu-west-1:0123456789:targetgroup/prod-tg/8f858d88ba9c836c "Root=1-xxxxx-yyyyyyyyyyyyyyyyyyyyy" "elb01.prod.com" "arn:aws:acm:eu-west-1:0123456789:certificate/bbbbbbbb-1cbf-4f99-aaaa-cccccccccccc" 0 2019-02-02T00:14:07.435000Z "forward" "-" "-"`), + ".*", + ".*", + ".*", + "GET", + true, + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + b := []byte{} + buff := bytes.NewBuffer(b) + gw := gzip.NewWriter(buff) + gw.Write(tc.testData) + gw.Close() + a := Accesslog{} + a.Content = buff + a.RowFilter = Filter{ + ClientIP: tc.clientIP, + HTTPmethod: tc.HTTPmethod, + ElbStatusCode: tc.ElbStatusCode, + TargetStatusCode: tc.targetStatusCode, + } + result := a.Cat() + if len(result) == 0 { + t.Fatalf("test: %s failed to find match", tc.name) + } + + }) + } + +} diff --git a/logworker/logworker.go b/logworker/logworker.go new file mode 100644 index 0000000..f85ed71 --- /dev/null +++ b/logworker/logworker.go @@ -0,0 +1,196 @@ +package logworker + +import ( + "fmt" + "os" + "regexp" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials/stscreds" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/s3" + "github.com/aws/aws-sdk-go/service/s3/s3manager" + "github.com/sirupsen/logrus" + "github.com/spf13/viper" +) + +type ( + LogWorker struct { + Config *AWSconfiguration + S3 *s3.S3 + S3Downloader *s3manager.Downloader + Configuration *Configuration + AccessLogFilter *AccessLogFilter + } + //AWSconfiguration --.. + AWSconfiguration struct { + Region string + Profile string + } + Configuration struct { + Bucket string + Prefix string + } + AccessLogFilter struct { + matchString string + AwsAccountID string + Region string + LoadBalancerID string + IPaddress string + RandomString string + StartTime time.Time + EndTime time.Time + matcher *regexp.Regexp + } +) + +var ( + Logger *logrus.Logger +) + +func init() { + // Log as JSON instead of the default ASCII formatter. + Logger = logrus.New() + Logger.SetFormatter(&logrus.JSONFormatter{}) + + // Output to stdout instead of the default stderr + // Can be any io.Writer, see below for File example + Logger.SetOutput(os.Stdout) + +} + +func newFilter(accessLogFilter *AccessLogFilter) *regexp.Regexp { + matchString := fmt.Sprintf("^(%s)_(elasticloadbalancing)_(%s)_(%s)_(%s)_(%s)_(%s).log.gz$", + accessLogFilter.AwsAccountID, + accessLogFilter.Region, + accessLogFilter.LoadBalancerID, + ".*", + accessLogFilter.IPaddress, + accessLogFilter.RandomString, + ) + + regexp, err := regexp.Compile(matchString) + if err != nil { + Logger.Fatalf("Failed to compile matchstring. Gott error: %v", err) + } + + return regexp +} + +func NewLogWorker( + awsConfiguration *AWSconfiguration, + configuration *Configuration, + accessLogFilter *AccessLogFilter, +) *LogWorker { + logWorker := LogWorker{} + logWorker.Configuration = configuration + logWorker.AccessLogFilter = accessLogFilter + logWorker.AccessLogFilter.matcher = newFilter(accessLogFilter) + + awsCfg := aws.Config{} + if awsConfiguration.Region != "" { + awsCfg.Region = &awsConfiguration.Region + } + + awsSessionOpts := session.Options{ + Config: awsCfg, + AssumeRoleTokenProvider: stscreds.StdinTokenProvider, + SharedConfigState: session.SharedConfigEnable, + } + + if awsConfiguration.Profile != "" { + awsSessionOpts.Profile = awsConfiguration.Profile + } + + sess := session.Must(session.NewSessionWithOptions(awsSessionOpts)) + + logWorker.S3 = s3.New(sess) + logWorker.S3Downloader = s3manager.NewDownloader(sess) + + return &logWorker +} + +func (l *LogWorker) List() []string { + + var accessLogs []string + input := &s3.ListObjectsV2Input{ + Bucket: aws.String(l.Configuration.Bucket), + Prefix: aws.String(fmt.Sprintf("%s%s", l.Configuration.Prefix, l.AccessLogFilter.AccesslogPath())), + Delimiter: aws.String("/"), + MaxKeys: aws.Int64(200), + } + err := l.S3.ListObjectsV2Pages(input, + func(page *s3.ListObjectsV2Output, lastPage bool) bool { + for _, val := range page.Contents { + accessLog := strings.Split(*val.Key, "/")[len(strings.Split(*val.Key, "/"))-1] + if l.AccessLogFilter.matcher.MatchString(accessLog) { + accessLogs = append(accessLogs, accessLog) + } + } + return true + }) + if err != nil { + fmt.Println(err) + } + return accessLogs +} + +func (a *AccessLogFilter) AccesslogPath() string { + return fmt.Sprintf("/AWSLogs/%s/elasticloadbalancing/%s/%s/", a.AwsAccountID, a.Region, a.StartTime.Format("2006/01/02")) +} + +func (a *AccessLogFilter) beforeEndTime(accessLog string) bool { + accessLogEndTimeStr := strings.Split(accessLog, "_")[4] + accessLogEndTimeStamp, err := time.Parse("20060102T1504Z", accessLogEndTimeStr) + if err != nil { + Logger.Fatalf("failed to parse timestamp for accesslog name") + } + accessLogStartTimeStamp := accessLogEndTimeStamp.Add(-5 * time.Minute) + + if (a.StartTime.Before(accessLogStartTimeStamp) || a.StartTime == accessLogStartTimeStamp) && + (a.EndTime.After(accessLogEndTimeStamp) || accessLogEndTimeStamp == a.EndTime) { + Logger.Debugf("1 if. aEndTimeStamp: %v, endFilter: %v \n", accessLogEndTimeStamp.Format("15:04"), a.EndTime.Format("15:04")) + return true + } else if (a.StartTime.After(accessLogStartTimeStamp) && a.StartTime.Before(accessLogEndTimeStamp)) && + (a.EndTime.Before(accessLogEndTimeStamp) && a.EndTime.After(a.StartTime)) { + Logger.Debugln("2 if") + return true + } else if (a.StartTime.Before(accessLogStartTimeStamp) || a.StartTime == accessLogStartTimeStamp) && + (a.EndTime.Before(accessLogEndTimeStamp) && a.EndTime.After(a.StartTime) && a.EndTime.After(accessLogStartTimeStamp)) { + Logger.Debugln("3 if") + return true + } else if (a.EndTime.After(accessLogEndTimeStamp) || a.EndTime == accessLogEndTimeStamp) && + (a.StartTime.After(accessLogStartTimeStamp) && a.StartTime.Before(accessLogEndTimeStamp)) { + Logger.Debugln("4 if") + return true + } + return false +} + +func NewAccessLogFilter() AccessLogFilter { + + startTime, err := time.Parse("2006-01-02 15:04:05", viper.GetString("start-time")) + if err != nil { + Logger.Fatalf("Failed to parse start time. Gott error: %v", err) + fmt.Println("failed to parse starttime") + } + + accessLogFilter := AccessLogFilter{} + accessLogFilter.AwsAccountID = viper.GetString("aws-account-id") + accessLogFilter.Region = viper.GetString("region") + accessLogFilter.StartTime = startTime // time.Now() + accessLogFilter.LoadBalancerID = viper.GetString("load-balancer-id") + accessLogFilter.IPaddress = viper.GetString("ip-address") + accessLogFilter.RandomString = viper.GetString("random-string") + + return accessLogFilter +} + +func NewConfiguration() Configuration { + return Configuration{ + Bucket: viper.GetString("s3-bucket"), + Prefix: viper.GetString("s3-prefix"), + } +} diff --git a/logworker/logworker_test.go b/logworker/logworker_test.go new file mode 100644 index 0000000..fa13ac1 --- /dev/null +++ b/logworker/logworker_test.go @@ -0,0 +1,174 @@ +package logworker + +import ( + "fmt" + "strings" + "testing" + "time" +) + +func TestAccessLogFilter(t *testing.T) { + + tt := []struct { + name string + accessLogFilter AccessLogFilter + accessLogPath string + }{ + {"test1", + AccessLogFilter{ + + AwsAccountID: "00000000111111", + Region: "eu-west-1", + StartTime: time.Now(), + }, + fmt.Sprintf("/AWSLogs/00000000111111/elasticloadbalancing/eu-west-1/%s/", time.Now().Format("2006/01/02")), + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + if tc.accessLogFilter.AccesslogPath() != tc.accessLogPath { + t.Fatalf("accespath test %v should be %v; got %v", tc.name, tc.accessLogFilter.AccesslogPath(), tc.accessLogPath) + } + }) + } + +} + +func TestMatcher(t *testing.T) { + tt := []struct { + name string + AwsAccountID string + Region string + LoadBalancerID string + IPaddress string + RandomString string + in string + }{ + { + "match-everything", + ".*", + ".*", + ".*", + ".*", + ".*", + "0123456789_elasticloadbalancing_eu-west-1_elb-prod.32435435_20190223T1610Z_10.205.19.102_34cjbbr9.log.gz", + }, + { + "match-accountid", + "0123456789", + ".*", + ".*", + ".*", + ".*", + "0123456789_elasticloadbalancing_eu-west-1_elb-prod.32435435_20190223T1610Z_10.205.19.102_34cjbbr9.log.gz", + }, + { + "match-loadbalancerid", + ".*", + ".*", + "elb-prod.32435435", + ".*", + ".*", + "0123456789_elasticloadbalancing_eu-west-1_elb-prod.32435435_20190223T1610Z_10.205.19.102_34cjbbr9.log.gz", + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + accessLogFilter := AccessLogFilter{} + accessLogFilter.AwsAccountID = tc.AwsAccountID + accessLogFilter.Region = tc.Region + accessLogFilter.LoadBalancerID = tc.LoadBalancerID + accessLogFilter.IPaddress = tc.IPaddress + accessLogFilter.RandomString = tc.RandomString + matcher := newFilter(&accessLogFilter) + if !matcher.MatchString(tc.in) { + t.Fatalf("") + } + }) + } +} + +func TestStartTimeEndTime(t *testing.T) { + tt := []struct { + name string + StartTime string + EndTime string + in string + out bool + }{ + { + "FalseStartEndFilterAfterAccessLogTimestamp", + "2019-02-23 15:00:00", + "2019-02-23 16:10:00", + "0123456789_elasticloadbalancing_eu-west-1_elb-prod.32435435_20190223T1110Z_10.205.19.102_34cjbbr9.log.gz", + false, + }, + { + "FalseStartEndFilterBeforeAccesslogTimestamå", + "2019-02-23 09:00:00", + "2019-02-23 10:10:00", + "0123456789_elasticloadbalancing_eu-west-1_elb-prod.32435435_20190223T1110Z_10.205.19.102_34cjbbr9.log.gz", + false, + }, + { + "TrueBetweenStartEnd", + "2019-02-23 15:00:00", + "2019-02-23 23:00:00", + "0123456789_elasticloadbalancing_eu-west-1_elb-prod.32435435_20190223T1610Z_10.205.19.102_34cjbbr9.log.gz", + true, + }, + { + "TrueSameEndTime", + "2019-02-23 15:00:00", + "2019-02-23 16:10:00", + "0123456789_elasticloadbalancing_eu-west-1_elb-prod.32435435_20190223T1610Z_10.205.19.102_34cjbbr9.log.gz", + true, + }, + { + "TrueSameStartTime", + "2019-02-23 14:50:00", + "2019-02-23 16:10:00", + "0123456789_elasticloadbalancing_eu-west-1_elb-prod.32435435_20190223T1455Z_10.205.19.102_34cjbbr9.log.gz", + true, + }, + { + "TrueStartAndEndTimeBetweenAcessLogTimestamp", + "2019-02-23 14:52:00", + "2019-02-23 14:54:00", + "0123456789_elasticloadbalancing_eu-west-1_elb-prod.32435435_20190223T1455Z_10.205.19.102_34cjbbr9.log.gz", + true, + }, + { + "TrueStartFilterAfterAcessLogFirstTimestamp", + "2019-02-23 14:52:00", + "2019-02-23 16:00:00", + "0123456789_elasticloadbalancing_eu-west-1_elb-prod.32435435_20190223T1455Z_10.205.19.102_34cjbbr9.log.gz", + true, + }, + { + "TrueEndFilterBeforeAcessLogEndTimestamp", + "2019-02-23 14:45:00", + "2019-02-23 14:54:00", + "0123456789_elasticloadbalancing_eu-west-1_elb-prod.32435435_20190223T1455Z_10.205.19.102_34cjbbr9.log.gz", + true, + }, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + sTime, _ := time.Parse("2006-01-02 15:04:05", tc.StartTime) + eTime, _ := time.Parse("2006-01-02 15:04:05", tc.EndTime) + accessLogFilter := AccessLogFilter{} + accessLogFilter.StartTime = sTime + accessLogFilter.EndTime = eTime + + if !accessLogFilter.beforeEndTime(tc.in) == tc.out { + t.Fatalf("startTime: %v, endTime: %v, accesslog timestamp: %v", + sTime.Format("20060102T15:04Z"), + eTime.Format("20060102T15:04Z"), + strings.Split(tc.in, "_")[4]) + } + }) + } +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..1e08d29 --- /dev/null +++ b/main.go @@ -0,0 +1,29 @@ +// Copyright © 2019 Björn Ahl +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package main + +import ( + "github.com/dbgeek/elblogcat/cmd" +) + +func main() { + cmd.Execute() +}