├── .gitignore ├── main.go ├── config ├── loga.yaml ├── config.go └── config_test.go ├── loga ├── errorhandler.go ├── loga_helpers.go ├── loga.go └── elastic_runner.go ├── Makefile ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | loga_* 2 | loga.yaml 3 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/malnick/logasaurus/loga" 4 | 5 | func main() { 6 | loga.Start() 7 | } 8 | -------------------------------------------------------------------------------- /config/loga.yaml: -------------------------------------------------------------------------------- 1 | flagdefinedquery: "" 2 | flagconfdefinedquery: "" 3 | flagversion: false 4 | define_service: {} 5 | sync_interval: 5 6 | sync_depth: 10 7 | elasticsearch_url: "" 8 | elasticsearch_port: "9200" 9 | elasticsearch_index: "" 10 | highlight: true 11 | start_time: 0 12 | count: 500 13 | log_verbose: false 14 | searchhost: false 15 | -------------------------------------------------------------------------------- /loga/errorhandler.go: -------------------------------------------------------------------------------- 1 | package loga 2 | 3 | import ( 4 | "os" 5 | 6 | log "github.com/Sirupsen/logrus" 7 | ) 8 | 9 | func BasicCheckOrExit(err error) { 10 | if err != nil { 11 | log.Error(err) 12 | os.Exit(1) 13 | } 14 | } 15 | 16 | func CheckElasticResponse(response *ESResponse) { 17 | if response.Status != 0 { 18 | log.Errorf("Elastic search returned an error handling request, run in debug mode (-v) to see complete response. HTTP code: %d", response.Status) 19 | os.Exit(1) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION := $(shell git describe --tags) 2 | REVISION := $(shell git rev-parse --short HEAD) 3 | 4 | BINARY_NAME := loga 5 | 6 | LDFLAGS := -X github.com/malnick/logasaurus/config.VERSION=$(VERSION) -X github.com/malnick/logasaurus/config.REVISION=$(REVISION) 7 | 8 | FILES := $(shell go list ./... | grep -v vendor) 9 | 10 | all: test install 11 | 12 | test: 13 | @echo "+$@" 14 | go test $(FILES) -cover 15 | 16 | build: 17 | @echo "+$@" 18 | go build -v -o loga_$(VERSION) -ldflags '$(LDFLAGS)' main.go 19 | 20 | install: 21 | @echo "+$@" 22 | go install -v -ldflags '$(LDFLAGS)' $(FILES) 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 Jeff Malnick 3 | 4 | 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: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | 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. 9 | -------------------------------------------------------------------------------- /loga/loga_helpers.go: -------------------------------------------------------------------------------- 1 | package loga 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "strings" 7 | 8 | log "github.com/Sirupsen/logrus" 9 | "github.com/mgutz/ansi" 10 | ) 11 | 12 | func highlightQuery(line string, query string) { 13 | // Split query into multiple parts for regex 14 | q := strings.Split(query, " ") 15 | // Match the string 16 | match, err := regexp.Compile(q[0]) 17 | if err != nil { 18 | panic(err) 19 | } 20 | 21 | // Split our line into an ary 22 | lineAry := strings.Split(line, " ") 23 | // Iterate the ary, finding the string match 24 | for i, s := range lineAry { 25 | if match.MatchString(s) { 26 | // Color just the string which matches 27 | hlQuery := ansi.Color(s, "yellow:black") 28 | // Thren break down into three parts 29 | lpt1 := lineAry[:i] 30 | lpt2 := lineAry[i:] 31 | lpt2 = append(lpt2[:0], lpt2[1:]...) 32 | // Contatenate back together 33 | part1 := strings.Join(lpt1, " ") 34 | part2 := strings.Join(lpt2, " ") 35 | final := []string{part1, hlQuery, part2} 36 | finalHl := strings.Join(final, " ") 37 | // Print the final output 38 | //log.Info(finalHl) 39 | fmt.Println(finalHl) 40 | } 41 | } 42 | } 43 | 44 | func setLogger(verbose bool) { 45 | if verbose { 46 | log.SetLevel(log.DebugLevel) 47 | log.Debug("DEBUG Logger") 48 | } else { 49 | log.SetLevel(log.InfoLevel) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /loga/loga.go: -------------------------------------------------------------------------------- 1 | package loga 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | log "github.com/Sirupsen/logrus" 8 | "github.com/malnick/logasaurus/config" 9 | ) 10 | 11 | func Start() { 12 | fmt.Println(` . . `) 13 | fmt.Println(` / '. .' \ `) 14 | fmt.Println(` .---. < > < > .---. `) 15 | fmt.Println(` | \ \ - ~ ~ - / / | `) 16 | fmt.Println(` ~-..-~ ~-..-~ `) 17 | fmt.Println(` \~~~\.' './~~~/ `) 18 | fmt.Println(` .-~~^-. \__/ \__/ `) 19 | fmt.Println(`.' O \ / / \ \ `) 20 | fmt.Println(`(_____' \._.' | } \/~~~/ `) 21 | fmt.Println(` ----. / } | / \__/ `) 22 | fmt.Println(` \-. | / | / \.,~~| `) 23 | fmt.Println(` ~-.__| /_ - ~ ^| /- _ \..-' f: f: `) 24 | fmt.Println(` | / | / ~-. -. _||_||_ `) 25 | fmt.Println(` |_____| |_____| ~ - . _ _ _ _ _>`) 26 | fmt.Println(`██╗ ██████╗ ██████╗ █████╗ ███████╗ █████╗ ██╗ ██╗██████╗ ██╗ ██╗███████╗`) 27 | fmt.Println(`██║ ██╔═══██╗██╔════╝ ██╔══██╗██╔════╝██╔══██╗██║ ██║██╔══██╗██║ ██║██╔════╝`) 28 | fmt.Println(`██║ ██║ ██║██║ ███╗███████║███████╗███████║██║ ██║██████╔╝██║ ██║███████╗`) 29 | fmt.Println(`██║ ██║ ██║██║ ██║██╔══██║╚════██║██╔══██║██║ ██║██╔══██╗██║ ██║╚════██║`) 30 | fmt.Println(`███████╗╚██████╔╝╚██████╔╝██║ ██║███████║██║ ██║╚██████╔╝██║ ██║╚██████╔╝███████║`) 31 | fmt.Println(`╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝`) 32 | fmt.Println() 33 | config := config.ParseArgsReturnConfig(os.Args[1:]) 34 | setLogger(config.LogVerbose) 35 | if config.FlagVersion { 36 | config.PrintVersion() 37 | return 38 | } 39 | query, err := config.GetDefinedQuery() 40 | BasicCheckOrExit(err) 41 | log.WithFields(log.Fields{ 42 | "Query": query, 43 | "Elastic Host": config.ElasticsearchURL, 44 | "Elastic Port": config.ElasticsearchPort, 45 | }).Info("Elastic Runner") 46 | // Roll into the query loop 47 | elasticRunner(query, config) 48 | } 49 | -------------------------------------------------------------------------------- /loga/elastic_runner.go: -------------------------------------------------------------------------------- 1 | package loga 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "strings" 10 | "time" 11 | 12 | log "github.com/Sirupsen/logrus" 13 | "github.com/malnick/logasaurus/config" 14 | 15 | "github.com/mgutz/ansi" 16 | ) 17 | 18 | type ESRequest struct { 19 | Size int `json:"size"` 20 | Sort struct { 21 | Timestamp string `json:"@timestamp"` 22 | } `json:"sort"` 23 | Query struct { 24 | Filtered struct { 25 | Query struct { 26 | QueryString struct { 27 | AnalyzeWildcard string `json:"analyze_wildcard"` 28 | Query string `json:"query"` 29 | } `json:"query_string"` 30 | } `json:"query"` 31 | Filter struct { 32 | Bool struct { 33 | Must []ESMust `json:"must"` 34 | MustNot []ESMustNot `json:"must_not"` 35 | } `json:"bool"` 36 | } `json:"filter"` 37 | } `json:"filtered"` 38 | } `json:"query"` 39 | } 40 | 41 | type ESMust struct { 42 | Range struct { 43 | Timestamp struct { 44 | Gte interface{} `json:"gte"` 45 | Lte interface{} `json:"lte"` 46 | } `json:"@timestamp"` 47 | } `json:"range"` 48 | } 49 | 50 | type ESMustNot struct{} 51 | 52 | func (esRequest *ESRequest) makeRequest(c *config.Config) (ESResponse, error) { 53 | var esResponse ESResponse 54 | 55 | jsonpost, err := json.MarshalIndent(&esRequest, "", "\t") 56 | if err != nil { 57 | return esResponse, err 58 | } 59 | log.Debugf("Elastic Search Request:\n %s", string(jsonpost)) 60 | 61 | // Craft the request URI 62 | queryURL := strings.Join([]string{"http://", c.ElasticsearchURL, ":", c.ElasticsearchPort, "/_search?pretty"}, "") 63 | log.Debug("Query URI: ", queryURL) 64 | 65 | // Make request 66 | req, err := http.NewRequest("POST", queryURL, bytes.NewBuffer(jsonpost)) 67 | if err != nil { 68 | return esResponse, err 69 | } 70 | 71 | client := &http.Client{} 72 | resp, err := client.Do(req) 73 | if err != nil { 74 | return esResponse, err 75 | } 76 | defer resp.Body.Close() 77 | 78 | jsonRespBody, err := ioutil.ReadAll(resp.Body) 79 | if err != nil { 80 | return esResponse, err 81 | } 82 | log.Debugf("Elastic Search Response:\n%s", string(jsonRespBody)) 83 | 84 | err = json.Unmarshal(jsonRespBody, &esResponse) 85 | if err != nil { 86 | return esResponse, err 87 | } 88 | CheckElasticResponse(&esResponse) 89 | 90 | return esResponse, nil 91 | } 92 | 93 | type Hit struct { 94 | Source struct { 95 | Host string `json:"host"` 96 | Message string `json:"message"` 97 | } `json:"_source"` 98 | } 99 | 100 | type ESResponse struct { 101 | Hits struct { 102 | Hits []Hit `json:"hits"` 103 | } `json:"hits"` 104 | Status int `json:"status"` 105 | } 106 | 107 | func (esResponse *ESResponse) printResponse(c config.Config, service string) { 108 | // Print 109 | for _, hit := range esResponse.Hits.Hits { 110 | if c.SearchHost { 111 | message := hit.Source.Message 112 | host := ansi.Color(hit.Source.Host, "cyan:black") 113 | withHost := strings.Join([]string{host, " ", message}, "") 114 | if c.Highlight { 115 | highlightQuery(withHost, service) 116 | } else { 117 | fmt.Println(withHost) 118 | } 119 | } else { 120 | message := hit.Source.Message 121 | if c.Highlight { 122 | highlightQuery(message, service) 123 | } else { 124 | fmt.Println(message) 125 | } 126 | } 127 | } 128 | } 129 | 130 | func elasticRunner(service string, c config.Config) { 131 | var ( 132 | esRequest = ESRequest{} 133 | must = ESMust{} 134 | lte = time.Now().Add(time.Duration(-c.StartTime) * time.Minute) 135 | ) 136 | for syncCount := 0; syncCount >= 0; syncCount++ { 137 | // Set time: last 10min or last sync_interval 138 | if syncCount > 0 { 139 | must.Range.Timestamp.Gte = lte.Add(time.Duration(-c.SyncInterval) * time.Second) 140 | } else { 141 | must.Range.Timestamp.Gte = lte.Add(time.Duration(-c.SyncDepth) * time.Minute) 142 | } 143 | 144 | must.Range.Timestamp.Lte = lte 145 | 146 | esRequest.Size = c.Count 147 | esRequest.Sort.Timestamp = "asc" 148 | esRequest.Query.Filtered.Query.QueryString.AnalyzeWildcard = "true" 149 | esRequest.Query.Filtered.Query.QueryString.Query = string(service) 150 | esRequest.Query.Filtered.Filter.Bool.Must = []ESMust{must} 151 | 152 | esResponse, err := esRequest.makeRequest(&c) 153 | BasicCheckOrExit(err) 154 | 155 | esResponse.printResponse(c, service) 156 | time.Sleep(time.Second * time.Duration(c.SyncInterval)) 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "errors" 5 | "flag" 6 | "fmt" 7 | "io/ioutil" 8 | "os" 9 | 10 | "gopkg.in/yaml.v2" 11 | 12 | log "github.com/Sirupsen/logrus" 13 | ) 14 | 15 | var ( 16 | VERSION = "UNSET" 17 | REVISION = "UNSET" 18 | ) 19 | 20 | type Config struct { 21 | FlagDefinedQuery string `yaml:",omitempty"` 22 | FlagConfDefinedQuery string `yaml:",omitempty"` 23 | FlagVersion bool `yaml:",omitempty"` 24 | ConfDefinedQueries map[string]string `yaml:"defined_queries"` 25 | SyncInterval int `yaml:"sync_interval"` 26 | SyncDepth int `yaml:"sync_depth"` 27 | ElasticsearchURL string `yaml:"elasticsearch_url"` 28 | ElasticsearchPort string `yaml:"elasticsearch_port"` 29 | ElasticsearchIndex string `yaml:"elasticsearch_index"` 30 | Highlight bool `yaml:"highlight_query"` 31 | StartTime int `yaml:"start_time"` 32 | Count int `yaml:"count"` 33 | LogVerbose bool `yaml:"log_verbose"` 34 | SearchHost bool `yaml:"search_host,omitempty"` 35 | logaConfigPath string `yaml:",omitempty"` 36 | } 37 | 38 | func basicCheckOrExit(err error) { 39 | if err != nil { 40 | log.Error(err) 41 | os.Exit(1) 42 | } 43 | } 44 | 45 | func defaultConfig() Config { 46 | return Config{ 47 | SyncInterval: 5, 48 | SyncDepth: 10, 49 | ElasticsearchPort: "9200", 50 | ElasticsearchURL: "localhost", 51 | SearchHost: false, 52 | Highlight: true, 53 | StartTime: 0, 54 | Count: 500, 55 | LogVerbose: false, 56 | logaConfigPath: "./loga.yaml", 57 | ConfDefinedQueries: map[string]string{ 58 | "example": "foo AND bar", 59 | }, 60 | } 61 | } 62 | 63 | func (c *Config) PrintVersion() { 64 | fmt.Printf("Logasaurus: Kibana for the CLI\nAuthor: Jeff Malnick\nVersion: %s\nRevision: %s\nLicense: Copyright(c) MIT License 2016 Jeff Malnick\n", VERSION, REVISION) 65 | } 66 | 67 | func (c *Config) fromLogaYaml() error { 68 | configFile, err := ioutil.ReadFile(c.logaConfigPath) 69 | if err != nil { 70 | log.Warnf("%s not found, writing with all defaults.", c.logaConfigPath) 71 | writeme, err := yaml.Marshal(&c) 72 | if err != nil { 73 | return err 74 | } 75 | 76 | if err = ioutil.WriteFile(c.logaConfigPath, []byte(writeme), 0644); err != nil { 77 | return err 78 | } 79 | } else { 80 | if err := yaml.Unmarshal(configFile, &c); err != nil { 81 | return err 82 | } 83 | } 84 | return nil 85 | } 86 | 87 | func (c *Config) GetDefinedQuery() (query string, err error) { 88 | if len(c.FlagDefinedQuery) > 0 { 89 | return c.FlagDefinedQuery, nil 90 | } else if len(c.FlagConfDefinedQuery) > 0 { 91 | if _, ok := c.ConfDefinedQueries[c.FlagConfDefinedQuery]; ok { 92 | return c.ConfDefinedQueries[c.FlagConfDefinedQuery], nil 93 | } 94 | } 95 | return query, errors.New("Must define (-d) a query on the CLI or in loga.yaml (specify they query key with -s)") 96 | } 97 | 98 | func (c *Config) setFlags(fs *flag.FlagSet) { 99 | fs.BoolVar(&c.LogVerbose, "v", c.LogVerbose, "Verbose logging option") 100 | fs.BoolVar(&c.Highlight, "h", c.Highlight, "Highlight search in output") 101 | fs.BoolVar(&c.FlagVersion, "version", false, "Print version and exit") 102 | 103 | fs.StringVar(&c.logaConfigPath, "c", c.logaConfigPath, "Path to loga.yaml") 104 | fs.StringVar(&c.FlagDefinedQuery, "d", c.FlagDefinedQuery, "Define a lookup on the CLI") 105 | fs.StringVar(&c.FlagConfDefinedQuery, "s", c.FlagConfDefinedQuery, "Name of definition in loga.yaml") 106 | fs.StringVar(&c.ElasticsearchURL, "e", c.ElasticsearchURL, "URL for Elastic Search") 107 | fs.StringVar(&c.ElasticsearchPort, "p", c.ElasticsearchPort, "Port for Elastic Search") 108 | fs.StringVar(&c.ElasticsearchIndex, "in", c.ElasticsearchIndex, "Elastic Search index") 109 | 110 | fs.IntVar(&c.StartTime, "st", c.StartTime, "Start time in minutes. Ex: -st 20 starts query 20 minutes ago.") 111 | fs.IntVar(&c.SyncInterval, "si", c.SyncInterval, "Query interval in seconds") 112 | fs.IntVar(&c.SyncDepth, "sd", c.SyncDepth, "Sync depth: how far back to go on initial query") 113 | } 114 | 115 | func ParseArgsReturnConfig(args []string) Config { 116 | logaFlags := flag.NewFlagSet("", flag.ContinueOnError) 117 | config := defaultConfig() 118 | config.fromLogaYaml() 119 | config.setFlags(logaFlags) 120 | logaFlags.Parse(args) 121 | return config 122 | } 123 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Build Status: [![Circle CI](https://circleci.com/gh/malnick/logasaurus/tree/master.svg?style=svg)](https://circleci.com/gh/malnick/logasaurus/tree/master) 2 | 3 | # Logasaurus 4 | Logasurous (loga command) is a command line utility that queries elasticsearch in realtime, so you can tail logs just like you used to. 5 | 6 | ## Build & Configure & Run 7 | 8 | ```make build``` 9 | 10 | Will result in a binary in the form ```loga_$VERSION-$REVISION``` being created in this directory. If you checkout a tag, the appendage will be only the tagged version. 11 | 12 | If you're on OSx I recommend placing the build binary in `/usr/local/bin` or somewhere else in your $PATH. 13 | 14 | Upon building, you can run loga for version information: 15 | 16 | ``` 17 | ./loga_$VERSION -version 18 | ``` 19 | 20 | ### Execute Once to Build loga.yaml Locally 21 | 22 | ```./loga_$VERSION``` 23 | 24 | Creates the default `loga.yaml` in the current directory: 25 | 26 | ```yaml 27 | defined_queries: 28 | example: foo AND bar 29 | sync_interval: 5 30 | sync_depth: 10 31 | elasticsearch_url: localhost 32 | elasticsearch_port: "9200" 33 | elasticsearch_index: "" 34 | highlight_query: true 35 | start_time: 0 36 | count: 500 37 | log_verbose: false 38 | ``` 39 | 40 | Update this file, replacing at minimum the default defined query (or you can override this on the CLI with a one-time query using `-d`), and the elasticsearch URL. 41 | 42 | Once done, execute your first lookup (example with defined query in config): 43 | 44 | ``` 45 | ./loga_$VERSION -s example 46 | ``` 47 | 48 | or do a one-time query on the CLI: 49 | 50 | ``` 51 | ./loga_$VERSION -d foo AND bar 52 | ``` 53 | 54 | ### Path to loga.yaml 55 | You can override the config location is `-c` - don't use ~ or other shell expansion, provide the fully qualified path if you use this option. 56 | 57 | ## Usage 58 | 59 | #### Defined query on the CLI: 60 | 61 | ```loga -d "some_query AND another_query"``` 62 | 63 | Will return matched messages from the last 10 minutes (see -sd override below) and resync backwards 5 seconds every 5 seconds (see -si override below). 64 | 65 | #### Defined service in loga.yaml: 66 | 67 | ```loga -s my_service_name``` 68 | 69 | Will return the query lookup from 'my_service_name' which should be in the 'define' section of the loga.yaml. 70 | 71 | loga will present the results from the search as a stream to stdin. Since the query is over standard http sockets, it'll return the query every 1s by default. 72 | 73 | ## Manpage 74 | 75 | **NAME** 76 | 77 | loga -- query ES logs on the CL 78 | 79 | **SYNOPSIS** 80 | 81 | loga [-d | --define string] [-i | --intervel time-in-seconds] [-v | --verbose] [-e | --elasticsearch-uri string-uri] [-p | --port elasticsearch-port] [-in | --es-index elasticsearch-index] [-c | --configuration path-to-config] 82 | 83 | **DESCRIPTION** 84 | 85 | The logasaurous (loga command) utility queries elasticsearch for logs based on a valid elasticsearch query. All requests are made to elasticsearch's REST endpoint over HTTP (HTTPS will be an option down the road). 86 | 87 | Logasaurous maintains a YAML configuration file where you can pre-set service definitions. You can leverage a one-time temporary service definition by using the ```define``` directive on the CLI. 88 | 89 | Many configurations in the config file can be overridden on the CLI as well. 90 | 91 | #### -c | Config 92 | Override the default configuration path. Default is ~/.loga.yaml on osx and /etc/loga.yaml on *nix distros. 93 | Ex: loga -c /fully/qualified/path/loga.yaml 94 | 95 | #### -co | Count 96 | Override the default count of queries to return. Default is 500. 97 | Ex: loga -d "some_query" -co 10 # Returns 10 queries from most recent. 98 | 99 | #### -d | Define 100 | A temporary service definition. Must be a valid elasticsearch query. Can not be used with -s. 101 | Ex: loga -d "some_value AND \"a-long-string\"" 102 | 103 | #### -e | Elasticsearch URL 104 | Override for `elasticsearch_uri` in config file. Default is localhost. 105 | Ex: loga -d "some_query" -e my.elastic.com 106 | 107 | #### -h | Enable Host Output 108 | Outputs the hostname for the log message before the message in cyan 109 | Ex: loga -d "some_query" -h 110 | 111 | #### -hl | Highlight Query 112 | Highlights the string in the message that contains a match to your query. Outputs in yellow. 113 | Ex: logo -d "some_query" -hl 114 | 115 | #### -s | Service Abstraction 116 | A defined service in the loga.yaml. Can not be used with -d. 117 | Ex: loga -s my_defined_service_in_loga.yaml 118 | 119 | #### -si | Sync Interval 120 | Time in seconds between elasticsearch queries. Default is 5s. 121 | Ex: loga -d "some_query" -si 10 122 | 123 | #### -sd | Sync Depth 124 | Time in minutes to sync backwards - only affects first sync. Start time is always time.Meow() but this might change. 125 | Ex: loga -d "some_query" -sd 120 126 | 127 | #### -st | Start Time 128 | Time in past in minutes to start the search. 129 | Ex: loga -d "some_query" -st 20 # Starts the search 20 minutes in the past to the sync depth, so a window 30-20 minutes ago if used with defualt sync depth of 10 minutes. It will update itself every 5 seconds by default. 130 | 131 | #### -p | Port 132 | Override for `elasticsearch_port` in config file. Default is 9300. 133 | Ex: loga -d "some_query" -p 4500 134 | 135 | #### -v | Verbose 136 | Verbose output. 137 | Ex: Figure it out. 138 | 139 | ## Tested 140 | ### Test Me 141 | 142 | `make test` 143 | 144 | ### Tested On 145 | - Elasticsearch: 1.4.4 - 2.3.2 146 | - Logstash: 1.5 - 2.3.2 147 | -------------------------------------------------------------------------------- /config/config_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "flag" 5 | "io/ioutil" 6 | "os" 7 | "testing" 8 | ) 9 | 10 | func TestDefaultConfig(t *testing.T) { 11 | testConfig := defaultConfig() 12 | expected := Config{ 13 | SyncInterval: 5, 14 | SyncDepth: 10, 15 | ElasticsearchPort: "9200", 16 | SearchHost: false, 17 | Highlight: true, 18 | StartTime: 0, 19 | Count: 500, 20 | LogVerbose: false, 21 | logaConfigPath: "./loga.yaml", 22 | } 23 | 24 | if testConfig.SyncInterval != expected.SyncInterval { 25 | t.Error("Expected default sync interval to be 5, got", testConfig.SyncInterval) 26 | } 27 | 28 | if testConfig.SyncDepth != expected.SyncDepth { 29 | t.Error("Expected default sync depth to be 10, got", testConfig.SyncDepth) 30 | } 31 | 32 | if testConfig.ElasticsearchPort != expected.ElasticsearchPort { 33 | t.Error("Expected default ES port to be 9200, got", testConfig.ElasticsearchPort) 34 | } 35 | 36 | if testConfig.SearchHost != expected.SearchHost { 37 | t.Error("Expected default search host to be true, got", testConfig.SearchHost) 38 | } 39 | 40 | if testConfig.Highlight != expected.Highlight { 41 | t.Error("Expected default highlight to be true, got", testConfig.Highlight) 42 | } 43 | 44 | if testConfig.StartTime != expected.StartTime { 45 | t.Error("Expected default start time to be 0, got", testConfig.StartTime) 46 | } 47 | 48 | if testConfig.Count != expected.Count { 49 | t.Error("Expected default count to be 500, got", testConfig.Count) 50 | } 51 | 52 | if testConfig.LogVerbose != expected.LogVerbose { 53 | t.Error("Expected default log verbose to be false, got", testConfig.LogVerbose) 54 | } 55 | 56 | if testConfig.logaConfigPath != expected.logaConfigPath { 57 | t.Error("Expected default config path to be", expected.logaConfigPath, "got", testConfig.logaConfigPath) 58 | } 59 | } 60 | 61 | func TestFromLogaYaml(t *testing.T) { 62 | var config = defaultConfig() 63 | var ( 64 | yamlConfig = ` 65 | sync_interval: 10 66 | count: 20 67 | ` 68 | badConfig = ` 69 | bar 70 | ` 71 | ) 72 | file, err := ioutil.TempFile(os.TempDir(), "loga_yaml") 73 | if err != nil { 74 | t.Error("Could not make temp file") 75 | } 76 | defer os.Remove(file.Name()) 77 | config.logaConfigPath = file.Name() 78 | file.Write([]byte(yamlConfig)) 79 | err = config.fromLogaYaml() 80 | if err != nil { 81 | t.Error("Count not get configuration from temp file") 82 | } 83 | 84 | if config.SyncInterval != 10 { 85 | t.Error("Expected sync interval from config to be 10, got", config.SyncInterval) 86 | } 87 | 88 | if config.Count != 20 { 89 | t.Error("Expected count from config to be 20, got", config.Count) 90 | } 91 | 92 | file.Write([]byte(badConfig)) 93 | err = config.fromLogaYaml() 94 | if err == nil { 95 | t.Error("expected error, got", err) 96 | } 97 | 98 | os.Remove(file.Name()) 99 | 100 | // Auto generates new file 101 | if err := config.fromLogaYaml(); err != nil { 102 | t.Error("expected no errors when config is removed, got", err) 103 | } 104 | } 105 | 106 | func TestGetDefinedQuery(t *testing.T) { 107 | var ( 108 | config = Config{} 109 | yamlConfig = ` 110 | defined_queries: 111 | test: "foo AND bar" 112 | ` 113 | ) 114 | file, err := ioutil.TempFile(os.TempDir(), "loga_yaml") 115 | if err != nil { 116 | t.Error(err) 117 | } 118 | defer os.Remove(file.Name()) 119 | 120 | config.logaConfigPath = file.Name() 121 | file.Write([]byte(yamlConfig)) 122 | if err := config.fromLogaYaml(); err != nil { 123 | t.Error(err) 124 | } 125 | 126 | config.FlagDefinedQuery = "bar AND foo" 127 | query, err := config.GetDefinedQuery() 128 | if err != nil { 129 | t.Error("expected no errors getting query, got", err) 130 | } 131 | if query != "bar AND foo" { 132 | t.Error("expected query to be 'bar AND foo', got", query) 133 | } 134 | 135 | config.FlagDefinedQuery = "" 136 | config.FlagConfDefinedQuery = "test" 137 | fooQuery, err := config.GetDefinedQuery() 138 | if err != nil { 139 | t.Error("expected no errors getting query, got", err) 140 | } 141 | if fooQuery != "foo AND bar" { 142 | t.Error("expected query to be 'foo AND bar', got", fooQuery) 143 | } 144 | } 145 | 146 | func TestSetFlags(t *testing.T) { 147 | var ( 148 | verbose = []string{"-v"} 149 | highlight = []string{"-h"} 150 | // version = []string{"-version"} 151 | // configPath = []string{"-c", "foo.yaml"} 152 | // define = []string{"-d", "foo"} 153 | // confDefine = []string{"-s", "bar"} 154 | // esUrl = []string{"-e", "foo.com"} 155 | // esPort = []string{"-p", "9300"} 156 | // esIndex = []string{"-in", "/foo"} 157 | // startTime = []string{"-st", "2"} 158 | // syncInt = []string{"si", "3"} 159 | // syncDep = []string{"sd", "4"} 160 | config = Config{} 161 | testFlags = flag.NewFlagSet("", flag.ContinueOnError) 162 | ) 163 | 164 | config.setFlags(testFlags) 165 | testFlags.Parse(verbose) 166 | if !config.LogVerbose { 167 | t.Error("Expected log verbose to be true, got", config.LogVerbose) 168 | } 169 | 170 | testFlags.Parse(highlight) 171 | if !config.Highlight { 172 | t.Error("Expected highlight to be true, got", config.Highlight) 173 | } 174 | 175 | } 176 | 177 | func TestParseArgeReturnConfig(t *testing.T) { 178 | var ( 179 | version = []string{"-version"} 180 | configPath = []string{"-c", "foo.yaml"} 181 | ) 182 | 183 | c := ParseArgsReturnConfig(version) 184 | if !c.FlagVersion { 185 | t.Error("expected veresion to be true, got", c.FlagVersion) 186 | } 187 | c = ParseArgsReturnConfig(configPath) 188 | if c.logaConfigPath != "foo.yaml" { 189 | t.Error("expected config path to be foo.yaml, got", c.logaConfigPath) 190 | } 191 | } 192 | --------------------------------------------------------------------------------