diff --git a/README.md b/README.md index 916cdb8..eaabc5e 100644 --- a/README.md +++ b/README.md @@ -3,15 +3,41 @@ Get your application events and errors to Trakerr via the *Trakerr API*. You will need your API key to send events to trakerr. -## Overview -- REST API version: 2.0.0 -- Package (SDK) version: 2.0.0 - ## Requirements. +Ruby 1.9.3+, git 2.0+, and curl 7.47.0+ + + +## 3-minute Integration Guide +First install [git and curl](#Install-git-and-curl). Once you have that complete, issue the command: + +```bash +gem "trakerr_client", :git => "git://github.com/trakerr-io/trakerr-ruby.git" +``` +or + +```bash +gem install trakerr_client +``` + +Then import the packages: +```ruby +require_relative 'trakerr_logger.rb' +``` + +Finally, the integration. Trakerr uses a string stream to catch the output of a logger and send it to trakerr. Trakerr afterwards writes the code to the stream, so it's possible to write that data elsewhere as required from the stream. +```ruby +logger = Trakerr::TrakerrLogger.new(Logger.new(STDOUT)) +``` +Simply use the logger as normal afterwards: +```ruby +logger.fatal('oops') -Ruby 1.9.3+ -and -git 2.0+ +#If you pass an exception into the logger, the backtrace will become more specific. +begin +rescue StandardError => e + logger.fatal(e) +end +``` ## Installation & Usage ### 1) Install git and curl @@ -32,40 +58,35 @@ For Windows, or if you aren't using a package manager, visit https://git-scm.com If you are on Windows, you may also need to install curl and configure your ruby to use it. Trakerr uses typhous to actually send the exception to us. Follow the instructions on the curl website for more information and Typhous's project page to finish setup. ### 2) gem install - Install [bundler](http://bundler.io/) and then you can issue this command to get the freshest version: -```sh +```bash gem "trakerr_client", :git => "git://github.com/trakerr-io/trakerr-ruby.git" ``` You can also install from ruby gems: -```sh +```bash gem install trakerr_client ``` for the latest stable release. -Then import the package: -```ruby -require 'trakerr/lib/trakerr' -``` - ## Detailed Integration Guide - Please follow the [installation procedure](#installation--usage) and you're set to add Trakerr to your project. All of these examples are included in test_app.rb. If you would like to generate some quick sample events, you may download test_app.rb and run it from the command line like so: -```sh -ruby test_app.rb <> +```bash +ruby test_app.rb ``` ### Package dependency Require the package: - ```ruby require 'trakerr/lib/trakerr' ``` -### Option 1: Sending a default error to Trakerr +### Option 1: Use the logger +See [above](#3-minute-Integration-Guide) to learn how to integrate with the built in ruby logger. + +### Option 2: Sending a default error to Trakerr A trivial case would involve calling `log` for a caught exception. ```ruby def main() @@ -75,17 +96,17 @@ def main() rescue ZeroDivisionError => exception #You can leave the hash empty if you would like to use the default values. #We recommend that you supply a user and a session for all events, - #and supplying an "evntname" and "evntmessage" for non errors. + #and supplying an "eventname" and "eventmessage" for non errors. testApp.log({"user"=>"jack@trakerr.io", "session"=>"7"}, exception) end end ``` -Along with the `"user"` and `"session"`; the hash can also take `"evntname"` and `"evntmessage"`. Note that these two will be filled in automatically for errors you rescue if you do not provide them, so we suggest giving them for non-errors. +Along with the `"user"` and `"session"`; the hash can also take `"eventname"` and `"eventmessage"`. Note that these two will be filled in automatically for errors you rescue if you do not provide them, so we suggest giving them for non-errors. `log` may also take in a log_level and a classification (We recommend you providing this **especially** if you send a warning or below), but will otherwise default all of the AppEvent properties. -### Option 2: Sending an error to Trakerr with Custom Data +### Option 3: Sending an error to Trakerr with Custom Data If you want to populate the `AppEvent` fully with custom properties (log only accepts the minimum set of useful custom properties to utilize Trakerr's rich feature set), you can manually create an `AppEvent` and populate it's fields. Pass it to the `SendEvent` to then send the AppEvent to Trakerr. See the `AppEvent` API for more information on it's properties. ```ruby @@ -103,7 +124,7 @@ def main() end ``` -### Option 3: Send a non-exception to Trakerr +### Option 4: Send a non-exception to Trakerr Trakerr accepts events that aren't errors. To do so, pass false to the CreateAppEvent Exception field to not attach a stacktrace to the event (if you don't need it). Be sure to pass values in to the rest of the parameters since the default values will most likely not be useful for you if you don't have a stacktrace! ```ruby def main() @@ -147,7 +168,6 @@ Name | Type | Description | Notes **contextDataCenterRegion** | **string** | Data center region. | Defaults to `nil` ## Documentation For Models - - [AppEvent](https://github.com/trakerr-io/trakerr-python/blob/master/generated/docs/AppEvent.md) ## Author diff --git a/generated/README.md b/generated/README.md index 188cf92..fb613fb 100644 --- a/generated/README.md +++ b/generated/README.md @@ -8,7 +8,7 @@ This SDK is automatically generated by the [Swagger Codegen](https://github.com/ - API version: 1.0.0 - Package version: 1.0.0 -- Build date: 2017-03-13T17:18:49.200-07:00 +- Build date: 2017-05-05T15:16:47.647-07:00 - Build package: class io.swagger.codegen.languages.RubyClientCodegen ## Installation diff --git a/generated/docs/AppEvent.md b/generated/docs/AppEvent.md index 2b7927d..eb4bb2e 100644 --- a/generated/docs/AppEvent.md +++ b/generated/docs/AppEvent.md @@ -5,7 +5,7 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **api_key** | **String** | API key generated for the application | **log_level** | **String** | (optional) Logging level, one of 'debug','info','warning','error', 'fatal', defaults to 'error' | [optional] -**classification** | **String** | (optional) one of 'error' or a custom string for non-errors, defaults to 'error' | +**classification** | **String** | (optional) one of 'issue' or a custom string for non-issues, defaults to 'issue' | **event_type** | **String** | type of the event or error (eg. NullPointerException) | **event_message** | **String** | message containing details of the event or error | **event_time** | **Integer** | (optional) event time in ms since epoch | [optional] @@ -24,6 +24,14 @@ Name | Type | Description | Notes **context_app_os_version** | **String** | (optional) OS version the application is running on | [optional] **context_data_center** | **String** | (optional) Data center the application is running on or connected to | [optional] **context_data_center_region** | **String** | (optional) Data center region | [optional] +**context_tags** | **Array<String>** | | [optional] +**context_url** | **String** | (optional) The full URL when running in a browser when the event was generated. | [optional] +**context_operation_time_millis** | **Integer** | (optional) duration that this event took to occur in millis. Example - database call time in millis. | [optional] +**context_cpu_percentage** | **Integer** | (optional) CPU utilization as a percent when event occured | [optional] +**context_memory_percentage** | **Integer** | (optional) Memory utilization as a percent when event occured | [optional] +**context_cross_app_correlation_id** | **String** | (optional) Cross application correlation ID | [optional] +**context_device** | **String** | (optional) Device information | [optional] +**context_app_sku** | **String** | (optional) Application SKU | [optional] **custom_properties** | [**CustomData**](CustomData.md) | | [optional] **custom_segments** | [**CustomData**](CustomData.md) | | [optional] diff --git a/generated/lib/trakerr_client/models/app_event.rb b/generated/lib/trakerr_client/models/app_event.rb index d292e6e..0d6645c 100644 --- a/generated/lib/trakerr_client/models/app_event.rb +++ b/generated/lib/trakerr_client/models/app_event.rb @@ -1,7 +1,7 @@ =begin -Trakerr API +#Trakerr API -Get your application events and errors to Trakerr via the *Trakerr API*. +#Get your application events and errors to Trakerr via the *Trakerr API*. OpenAPI spec version: 1.0.0 @@ -32,7 +32,7 @@ class AppEvent # (optional) Logging level, one of 'debug','info','warning','error', 'fatal', defaults to 'error' attr_accessor :log_level - # (optional) one of 'error' or a custom string for non-errors, defaults to 'error' + # (optional) one of 'issue' or a custom string for non-issues, defaults to 'issue' attr_accessor :classification # type of the event or error (eg. NullPointerException) @@ -88,6 +88,29 @@ class AppEvent # (optional) Data center region attr_accessor :context_data_center_region + attr_accessor :context_tags + + # (optional) The full URL when running in a browser when the event was generated. + attr_accessor :context_url + + # (optional) duration that this event took to occur in millis. Example - database call time in millis. + attr_accessor :context_operation_time_millis + + # (optional) CPU utilization as a percent when event occured + attr_accessor :context_cpu_percentage + + # (optional) Memory utilization as a percent when event occured + attr_accessor :context_memory_percentage + + # (optional) Cross application correlation ID + attr_accessor :context_cross_app_correlation_id + + # (optional) Device information + attr_accessor :context_device + + # (optional) Application SKU + attr_accessor :context_app_sku + attr_accessor :custom_properties attr_accessor :custom_segments @@ -138,6 +161,14 @@ def self.attribute_map :'context_app_os_version' => :'contextAppOSVersion', :'context_data_center' => :'contextDataCenter', :'context_data_center_region' => :'contextDataCenterRegion', + :'context_tags' => :'contextTags', + :'context_url' => :'contextURL', + :'context_operation_time_millis' => :'contextOperationTimeMillis', + :'context_cpu_percentage' => :'contextCpuPercentage', + :'context_memory_percentage' => :'contextMemoryPercentage', + :'context_cross_app_correlation_id' => :'contextCrossAppCorrelationId', + :'context_device' => :'contextDevice', + :'context_app_sku' => :'contextAppSku', :'custom_properties' => :'customProperties', :'custom_segments' => :'customSegments' } @@ -167,6 +198,14 @@ def self.swagger_types :'context_app_os_version' => :'String', :'context_data_center' => :'String', :'context_data_center_region' => :'String', + :'context_tags' => :'Array', + :'context_url' => :'String', + :'context_operation_time_millis' => :'Integer', + :'context_cpu_percentage' => :'Integer', + :'context_memory_percentage' => :'Integer', + :'context_cross_app_correlation_id' => :'String', + :'context_device' => :'String', + :'context_app_sku' => :'String', :'custom_properties' => :'CustomData', :'custom_segments' => :'CustomData' } @@ -264,6 +303,40 @@ def initialize(attributes = {}) self.context_data_center_region = attributes[:'contextDataCenterRegion'] end + if attributes.has_key?(:'contextTags') + if (value = attributes[:'contextTags']).is_a?(Array) + self.context_tags = value + end + end + + if attributes.has_key?(:'contextURL') + self.context_url = attributes[:'contextURL'] + end + + if attributes.has_key?(:'contextOperationTimeMillis') + self.context_operation_time_millis = attributes[:'contextOperationTimeMillis'] + end + + if attributes.has_key?(:'contextCpuPercentage') + self.context_cpu_percentage = attributes[:'contextCpuPercentage'] + end + + if attributes.has_key?(:'contextMemoryPercentage') + self.context_memory_percentage = attributes[:'contextMemoryPercentage'] + end + + if attributes.has_key?(:'contextCrossAppCorrelationId') + self.context_cross_app_correlation_id = attributes[:'contextCrossAppCorrelationId'] + end + + if attributes.has_key?(:'contextDevice') + self.context_device = attributes[:'contextDevice'] + end + + if attributes.has_key?(:'contextAppSku') + self.context_app_sku = attributes[:'contextAppSku'] + end + if attributes.has_key?(:'customProperties') self.custom_properties = attributes[:'customProperties'] end @@ -329,6 +402,14 @@ def ==(o) context_app_os_version == o.context_app_os_version && context_data_center == o.context_data_center && context_data_center_region == o.context_data_center_region && + context_tags == o.context_tags && + context_url == o.context_url && + context_operation_time_millis == o.context_operation_time_millis && + context_cpu_percentage == o.context_cpu_percentage && + context_memory_percentage == o.context_memory_percentage && + context_cross_app_correlation_id == o.context_cross_app_correlation_id && + context_device == o.context_device && + context_app_sku == o.context_app_sku && custom_properties == o.custom_properties && custom_segments == o.custom_segments end @@ -342,7 +423,7 @@ def eql?(o) # Calculates hash code according to all attributes. # @return [Fixnum] Hash code def hash - [api_key, log_level, classification, event_type, event_message, event_time, event_stacktrace, event_user, event_session, context_app_version, deployment_stage, context_env_name, context_env_language, context_env_version, context_env_hostname, context_app_browser, context_app_browser_version, context_app_os, context_app_os_version, context_data_center, context_data_center_region, custom_properties, custom_segments].hash + [api_key, log_level, classification, event_type, event_message, event_time, event_stacktrace, event_user, event_session, context_app_version, deployment_stage, context_env_name, context_env_language, context_env_version, context_env_hostname, context_app_browser, context_app_browser_version, context_app_os, context_app_os_version, context_data_center, context_data_center_region, context_tags, context_url, context_operation_time_millis, context_cpu_percentage, context_memory_percentage, context_cross_app_correlation_id, context_device, context_app_sku, custom_properties, custom_segments].hash end # Builds the object from hash diff --git a/test_app.rb b/test_app.rb index 585a081..8f695f2 100644 --- a/test_app.rb +++ b/test_app.rb @@ -17,7 +17,10 @@ =end require 'rubygems' +require 'logger' require_relative 'trakerr/lib/trakerr' +require_relative 'trakerr/lib/trakerr_formatter' +require_relative 'trakerr/lib/trakerr_writer' def main() argarr = ARGV @@ -26,39 +29,61 @@ def main() api_key = argarr[0] if argarr.length > 0 and api_key == "" testApp = Trakerr::TrakerrClient.new(api_key, "1.0", "development") + stream = Trakerr::TrakerrWriter.new(api_key, "2.0", "development") + + rlog = Logger.new(stream) + #rlog = Logger.new($stdout) + rlog.formatter = Trakerr::TrakerrFormatter.new + + begin + raise IOError, "Failed to open file" + rescue IOError => err + rlog.fatal err + end + + #Since we use streams (StringIO) to hook into the ruby logger, + #accessing stream after it has finished logging and event is simple. + #Rewind the stream, and then read it to extract data to whatever device you wish for. + #The example in the comments below prints out to console. The formatter does change how the event is formatted + #and the information given as the output to be pertinant and easy to parse by the stream hook, but I could probably write a complex regex for default + #if the demand is there. + #stream.rewind + #log = stream.read + #puts log #Send exception to Trakerr with default values. begin raise ZeroDivisionError, "Oh no!" - rescue ZeroDivisionError => exception + rescue ZeroDivisionError => er #You can leave the hash empty if you would like to use the default values. #We recommend that you supply a user and a session for all events, #and supplying an "evntname" and "evntmessage" for non errors. - testApp.log({"user"=>"jack@trakerr.io", "session"=>"7"}, exception) + testApp.log({"user"=>"jack@trakerr.io", "session"=>"7"}, er) end #Get an AppEvent to populate the class with custom data and then send it to Trakerr. #Simple custom data can be send through log. begin - raise ArgumentError - rescue ArgumentError => e - appev = testApp.CreateAppEvent(e, "Error") + raise RegexpError, "Help!" + rescue RegexpError => e + appev = testApp.create_app_event(e, "Error") appev.event_user = "john@trakerr.io" appev.event_session = "5" appev.context_app_browser = "Chrome" appev.context_app_browser_version = "57.x" - testApp.SendEvent(appev) + testApp.send_event(appev) end #Send a non Exception to Trakerr. - appev2 = testApp.CreateAppEvent(false, "Info", "User failed auth", "400 err", "User error") + appev2 = testApp.create_app_event(false, "Info", "User failed auth", "400 err", "User error") appev2.event_user = "jill@trakerr.io" appev2.event_session = "3" appev2.context_app_browser = "Edge" appev2.context_app_browser_version = "40.15063.0.0" + appev2.context_operation_time_millis = 5000 - testApp.SendEvent(appev2) + testApp.send_event(appev2) end diff --git a/trakerr/lib/event_trace_builder.rb b/trakerr/lib/event_trace_builder.rb index 2b76171..cdae790 100644 --- a/trakerr/lib/event_trace_builder.rb +++ b/trakerr/lib/event_trace_builder.rb @@ -1,181 +1,207 @@ -=begin -Trakerr API - -Get your application events and errors to Trakerr via the *Trakerr API*. - -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. -=end - -require "trakerr_client" - +# Trakerr API +# +# Get your application events and errors to Trakerr via the *Trakerr API*. +# +# 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. + +require 'trakerr_client' module Trakerr class EventTraceBuilder ## - #Gets the stactrace from the exception instance passed in. - #RETURNS: A Stacktrace object that contains the trace of the exception passed in. - #exc:Exception: The exception caught or rescued. + # Gets the stactrace from the exception instance passed in. + # RETURNS: A Stacktrace object that contains the trace of the exception passed in. + # exc:Exception: The exception caught or rescued. ## def self.get_stacktrace(exc) - raise ArgumentError, "get_stacktrace expects an exception instance." unless exc.is_a? Exception + raise ArgumentError, 'get_stacktrace expects an exception instance.' unless exc.is_a? Exception strace = Trakerr::Stacktrace.new - add_stack_trace(strace, exc) - return strace + add_stack_trace(strace, exc.class.name, exc.message, best_regexp_for(exc), exc.backtrace) + strace end - private + def self.get_logger_stacktrace(errtype, errmessage, stackarray) + raise ArgumentError, 'errtype and errmessage are expected strings' unless (errtype.is_a? String) && (errmessage.is_a? String) + raise ArgumentError, 'stackarray is expected to be an iterable with strings values' unless stackarray.respond_to?('each') + + strace = Trakerr::Stacktrace.new + add_stack_trace(strace, errtype, errmessage, best_regexp_guess(stackarray[0]), stackarray) + strace + end - ## - #Adds a InnerStackTrace to the Stacktrace object (which is a collection) - #strace:Stacktrace: The Stacktrace object to append the latest InnerStackTrace to. - #exc:Exception: The exception caught or rescued. - ## - def self.add_stack_trace(strace, exc) - raise ArgumentError, "add_stack_trace did not get passed in the correct arguments" unless exc.is_a? Exception and strace.instance_of? Stacktrace + private - newtrace = Trakerr::InnerStackTrace.new + def self.add_stack_trace(strace, errtype, errmessage, regex, stackarray) + newtrace = Trakerr::InnerStackTrace.new - newtrace.type = exc.class.name - newtrace.message = exc.message - newtrace.trace_lines = get_event_tracelines(best_regexp_for(exc), exc.backtrace) - strace.push(newtrace) - end + newtrace.type = errtype + newtrace.message = errmessage + newtrace.trace_lines = get_event_tracelines(regex, stackarray) + strace.push(newtrace) + end - ## - #Formats and returns a StackTraceLines object that holds the current stacktrace from the error. - #RETURNS: A StackTraceLines object that contains the parsed traceback. - #regex:RegularExpression: The regular expression to parse the stacktrace text with. - #errarray:String[]: An array of strings which each of which is a StackTrace string line. - ## - def self.get_event_tracelines(regex, errarray) - raise ArgumentError, "errarray should be an iterable object." unless errarray.respond_to?('each') + ## + # Formats and returns a StackTraceLines object that holds the current stacktrace from the error. + # RETURNS: A StackTraceLines object that contains the parsed traceback. + # regex:RegularExpression: The regular expression to parse the stacktrace text with. + # errarray:String[]: An array of strings which each of which is a StackTrace string line. + ## + def self.get_event_tracelines(regex, errarray) + raise ArgumentError, 'errarray should be an iterable object.' unless errarray.respond_to?('each') - stlines = Trakerr::StackTraceLines.new + stlines = Trakerr::StackTraceLines.new - errarray.each {|line| + errarray.each do |line| stline = Trakerr::StackTraceLine.new match = parse_stacktrace(regex, line) - stline.file, stline.line, stline.function = match[:file], match[:line], match[:function] + stline.file = match[:file] + stline.line = match[:line] + stline.function = match[:function] stlines.push(stline) - } - return stlines end + stlines + end - ## - #Parses each given line by the regex - #RETURNS: A match object with the capture groups file function and line set. - #regex:RegularExpression: The regular expression to parse the stacktrace text with. - #line:String: A string with the traceline to parce - ## - def self.parse_stacktrace(regex, line) - raise ArgumentError, "line should be a string." unless line.is_a? String + ## + # Parses each given line by the regex. + # RETURNS: A match object with the capture groups file function and line set. + # regex:RegularExpression: The regular expression to parse the stacktrace text with. + # line:String: A string with the traceline to parce + ## + def self.parse_stacktrace(regex, line) + raise ArgumentError, 'line should be a string.' unless line.is_a? String - match = regex.match(line) - return match if match + match = regex.match(line) + return match if match - raise RegexpError, "line does not fit any of the supported stacktraces." #TODO: Error handle this? - end + raise RegexpError, 'line does not fit any of the supported stacktraces.' # TODO: Error handle this? + end - def self.best_regexp_for(exc) - #add error check - if defined?(Java::JavaLang::Throwable) && exc.is_a?(Java::JavaLang::Throwable) - @@JAVA - elsif defined?(OCIError) && exc.is_a?(OCIError) - @@OCI - #elsif execjs_exception?(exception) - # Patterns::EXECJS disabled pending more complex test - else - @@RUBY - end + def self.best_regexp_for(exc) + # TODO: add error check + if defined?(Java::JavaLang::Throwable) && exc.is_a?(Java::JavaLang::Throwable) + @@JAVA + elsif defined?(OCIError) && exc.is_a?(OCIError) + @@OCI + # elsif execjs_exception?(exception) + # Patterns::EXECJS disabled pending more complex test + else + @@RUBY end + end - ## - # @return [Regexp] the pattern that matches standard Ruby stack frames, - # such as ./spec/notice_spec.rb:43:in `block (3 levels) in ' - @@RUBY = %r{\A - (?.+) # Matches './spec/notice_spec.rb' - : - (?\d+) # Matches '43' - :in\s - `(?.*)' # Matches "`block (3 levels) in '" - \z}x - - ## - # @return [Regexp] the pattern that matches JRuby Java stack frames, such - # as org.jruby.ast.NewlineNode.interpret(NewlineNode.java:105) - @@JAVA = %r{\A - (?.+) # Matches 'org.jruby.ast.NewlineNode.interpret' - \( - (? - (?:uri:classloader:/.+(?=:)) # Matches '/META-INF/jruby.home/protocol.rb' - | - (?:uri_3a_classloader_3a_.+(?=:)) # Matches 'uri_3a_classloader_3a_/gems/...' - | - [^:]+ # Matches 'NewlineNode.java' - ) - :? - (?\d+)? # Matches '105' - \) - \z}x + def self.best_regexp_guess(str) + # Guess the regex. Test each regex on the string and see which regex captures the most data. + # Use the one that captures the most. RegexErrors if none of the regexs are able to match + + java_match = @@JAVA.match(str) if defined?(Java::JavaLang::Throwable) + oci_match = @@OCI.match(str) # if defined?(OCIError) + ruby_match = @@RUBY.match(str) + java_count = 0 + ruby_count = 0 + oci_count = 0 + + ruby_match.captures.each { |item| ruby_count += 1 if item } if ruby_match + oci_match.captures.each { |item| oci_count += 1 if item } if oci_match + java_match.captures.each { |item| java_count += 1 if item } if java_match + + if ruby_count >= oci_count && ruby_count >= java_count && ruby_count > 0 + @@RUBY + elsif oci_count >= ruby_count && oci_count >= java_count && oci_count > 0 + @@OCI + elsif java_count >= ruby_count && java_count >= oci_count && java_count > 0 + @@JAVA + else + raise RegexpError, 'line does not fit any of the supported stacktraces.' + end + end - ## - # @return [Regexp] the pattern that tries to assume what a generic stack - # frame might look like, when exception's backtrace is set manually. - @@GENERIC = %r{\A - (?:from\s)? - (?.+) # Matches '/foo/bar/baz.ext' + ## + # @return [Regexp] the pattern that matches standard Ruby stack frames, + # such as ./spec/notice_spec.rb:43:in `block (3 levels) in ' + @@RUBY = %r{\A + (?.+) # Matches './spec/notice_spec.rb' : - (?\d+)? # Matches '43' or nothing - (?: - in\s`(?.+)' # Matches "in `func'" - | - :in\s(?.+) # Matches ":in func" - )? # ... or nothing + (?\d+) # Matches '43' + :in\s + `(?.*)' # Matches "`block (3 levels) in '" \z}x - ## - # @return [Regexp] the pattern that matches exceptions from PL/SQL such as - # ORA-06512: at "STORE.LI_LICENSES_PACK", line 1945 - # @note This is raised by https://github.com/kubo/ruby-oci8 - @@OCI = /\A - (?: - ORA-\d{5} - :\sat\s - (?:"(?.+)",\s)? - line\s(?\d+) - | - #{@@GENERIC} - ) - \z/x - - ## - # @return [Regexp] the pattern that matches CoffeeScript backtraces - # usually coming from Rails & ExecJS - @@EXECJS = /\A - (?: - # Matches 'compile ((execjs):6692:19)' - (?.+)\s\((?.+):(?\d+):\d+\) - | - # Matches 'bootstrap_node.js:467:3' - (?.+):(?\d+):\d+(?) - | - # Matches the Ruby part of the backtrace - #{@@RUBY} + ## + # @return [Regexp] the pattern that matches JRuby Java stack frames, such + # as org.jruby.ast.NewlineNode.interpret(NewlineNode.java:105) + @@JAVA = %r{\A + (?.+) # Matches 'org.jruby.ast.NewlineNode.interpret' + \( + (? + (?:uri:classloader:/.+(?=:)) # Matches '/META-INF/jruby.home/protocol.rb' + | + (?:uri_3a_classloader_3a_.+(?=:)) # Matches 'uri_3a_classloader_3a_/gems/...' + | + [^:]+ # Matches 'NewlineNode.java' ) - \z/x + :? + (?\d+)? # Matches '105' + \) + \z}x + + ## + # @return [Regexp] the pattern that tries to assume what a generic stack + # frame might look like, when exception's backtrace is set manually. + @@GENERIC = %r{\A + (?:from\s)? + (?.+) # Matches '/foo/bar/baz.ext' + : + (?\d+)? # Matches '43' or nothing + (?: + in\s`(?.+)' # Matches "in `func'" + | + :in\s(?.+) # Matches ":in func" + )? # ... or nothing + \z}x + + ## + # @return [Regexp] the pattern that matches exceptions from PL/SQL such as + # ORA-06512: at "STORE.LI_LICENSES_PACK", line 1945 + # @note This is raised by https://github.com/kubo/ruby-oci8 + @@OCI = /\A + (?: + ORA-\d{5} + :\sat\s + (?:"(?.+)",\s)? + line\s(?\d+) + | + #{@@GENERIC} + ) + \z/x + ## + # @return [Regexp] the pattern that matches CoffeeScript backtraces + # usually coming from Rails & ExecJS + @@EXECJS = /\A + (?: + # Matches 'compile ((execjs):6692:19)' + (?.+)\s\((?.+):(?\d+):\d+\) + | + # Matches 'bootstrap_node.js:467:3' + (?.+):(?\d+):\d+(?) + | + # Matches the Ruby part of the backtrace + #{@@RUBY} + ) + \z/x end -end \ No newline at end of file +end diff --git a/trakerr/lib/trakerr.rb b/trakerr/lib/trakerr.rb index 714fdef..a17558d 100644 --- a/trakerr/lib/trakerr.rb +++ b/trakerr/lib/trakerr.rb @@ -1,269 +1,277 @@ -=begin -Trakerr API - -Get your application events and errors to Trakerr via the *Trakerr API*. - -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. -=end - -require "event_trace_builder" -require "trakerr_client" -require "socket" -require "date" +# Trakerr API +# +# Get your application events and errors to Trakerr via the *Trakerr API*. +# +# 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 CO|| Backtrace.java_exception?(ex)NDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'event_trace_builder' +require 'trakerr_client' +require 'socket' +require 'date' module Trakerr class TrakerrClient + # #API Key + attr_accessor :api_key - ##API Key - attr_accessor :apiKey + # #App Version of the client the API is tying into. + attr_accessor :context_app_version - ##App Version of the client the API is tying into. - attr_accessor :contextAppVersion + # #Deployment stage of the codebade the API is tying into. + attr_accessor :context_deployment_stage - ##Deployment stage of the codebade the API is tying into. - attr_accessor :contextDeploymentStage + # #String name of the language being used. + attr_accessor :context_env_language - ##String name of the language being used. - attr_accessor :contextEnvLanguage + # #The name of the interpreter + attr_accessor :context_env_name - ##The name of the interpreter - attr_accessor :contextEnvName + ## context_env_version is the version of the interpreter the program is run on. + attr_accessor :context_env_version - ## ContextEnvVersion is the version of the interpreter the program is run on. - attr_accessor :contextEnvVersion + ## context_env_version is hostname of the pc running the code. + attr_accessor :context_env_version - ## ContextEnvHostname is hostname of the pc running the code. - attr_accessor :contextEnvHostname + ## context_app_os is the OS the program is running on. + attr_accessor :context_app_os - ## ContextAppOS is the OS the program is running on. - attr_accessor :contextAppOS + ## context_app_os_version is the version of the OS the code is running on. + attr_accessor :context_app_os_version - ## ContextAppOSVersion is the version of the OS the code is running on. - attr_accessor :contextAppOSVersion + ## context_app_browser is optional MVC and ASP.net applications the browser name the application is running on. + attr_accessor :context_app_browser - ## contextAppBrowser is optional MVC and ASP.net applications the browser name the application is running on. - attr_accessor :contextAppBrowser + ## context_app_browser_version is optional for MVC and ASP.net applications the browser version the application is running on. + attr_accessor :context_app_browser_version - ## contextAppBrowserVersion is optional for MVC and ASP.net applications the browser version the application is running on. - attr_accessor :contextAppBrowserVersion + ## context_data_center is the optional datacenter the code may be running on. + attr_accessor :context_data_center - ## ContextDatacenter is the optional datacenter the code may be running on. - attr_accessor :contextDataCenter - - ## ContextDatacenterRegion is the optional datacenter region the code may be running on. - attr_accessor :contextDataCenterRegion + ## context_data_center_region is the optional datacenter region the code may be running on. + attr_accessor :context_data_center_region ## - #Initializes the TrakerrClient class. - #apiKey:String: Should be your API key string. - #contextAppVersion:String: Should be the version of your application. - #contextEnvName:String: Should be the deployment stage of your program. + # Initializes the TrakerrClient class. + # api_key:String: Should be your API key string. + # context_app_version:String: Should be the version of your application. + # context_env_name:String: Should be the deployment stage of your program. ## - def initialize(apiKey, - contextAppVersion="1.0", - contextDeploymentStage="development") + def initialize(api_key, + context_app_version = '1.0', + context_deployment_stage = 'development') default_config = Trakerr::Configuration.default default_config.base_path = default_config.base_path - @apiKey = apiKey - @contextAppVersion = contextAppVersion - @contextDeploymentStage = contextDeploymentStage + @api_key = api_key + @context_app_version = context_app_version + @context_deployment_stage = context_deployment_stage - @contextEnvLanguage = "Ruby" - - if RUBY_PLATFORM == "java" - @contextEnvName = "jruby" - @contextEnvVersion = JRUBY_VERSION + @context_env_language = 'Ruby' +|| Backtrace.java_exception?(ex) + if RUBY_PLATFORM == 'java' + @context_env_name = 'jruby' + @context_env_version = JRUBY_VERSION else - @contextEnvName = "ruby" - @contextEnvVersion = RUBY_VERSION + @context_env_name = 'ruby' + @context_env_version = RUBY_VERSION end - @contextEnvHostname = Socket.gethostname - + @context_env_version = Socket.gethostname + host_os = RbConfig::CONFIG['host_os'] case host_os - when /mswin|msys|mingw|cygwin|bccwin|wince|emc/ - text = `systeminfo` - - @contextAppOS = GetTextFromLine(text, "OS Name:", "\n") - @contextAppOS.chomp! if @contextAppOS != nil - @contextAppOS.strip! if @contextAppOS != nil - - version = GetTextFromLine(text, "OS Version:", "\n").split - version[0].chomp! if version != nil - version[0].strip! if version != nil - @contextAppOSVersion = contextAppOSVersion || version[0] - - - when /darwin|mac os/ - text = `system_profiler SPSoftwareDataType` - - @contextAppOS = GetTextFromLine(text, "System Version:", "(").chomp.strip - @contextAppOSVersion = contextAppOSVersion || GetTextFromLine(text, "Kernel Version:", "\n").chomp.strip - - when /linux/, /solaris|bsd/ - #uname -s and -r - @contextAppOS = `uname -s`.chomp.strip - @contextAppOSVersion = contextAppOSVersion || `uname -r`.chomp.strip - end + when /mswin|msys|mingw|cygwin|bccwin|wince|emc/ + text = `systeminfo` - if @contextAppOS == nil - @contextAppOS = RbConfig::CONFIG["target_os"] + @context_app_os = get_text_from_line(text, 'OS Name:', "\n") + @context_app_os.chomp! unless @context_app_os.nil? + @context_app_os.strip! unless @context_app_os.nil? + + version = get_text_from_line(text, 'OS Version:', "\n").split + version[0].chomp! unless version.nil? + version[0].strip! unless version.nil? + @context_app_os_version = context_app_os_version || version[0] + + when /darwin|mac os/ + text = `system_profiler SPSoftwareDataType` + + @context_app_os = get_text_from_line(text, 'System Version:', '(').chomp.strip + @context_app_os_version = context_app_os_version || get_text_from_line(text, 'Kernel Version:', "\n").chomp.strip + + when /linux/, /solaris|bsd/ + # uname -s and -r + @context_app_os = `uname -s`.chomp.strip + @context_app_os_version = context_app_os_version || `uname -r`.chomp.strip end - if @contextAppOSVersion == nil - @contextAppOSVersion = RbConfig::CONFIG['host_os'] + + @context_app_os = RbConfig::CONFIG['target_os'] if @context_app_os.nil? + if @context_app_os_version.nil? + @context_app_os_version = RbConfig::CONFIG['host_os'] end - - @contextAppBrowser = contextAppBrowser - @contextAppBrowserVersion = contextAppBrowserVersion - @contextDataCenter = contextDataCenter - @contextDataCenterRegion = contextDataCenterRegion + + @context_app_browser = context_app_browser + @context_app_browser_version = context_app_browser_version + @context_data_center = context_data_center + @context_data_center_region = context_data_center_region api_client = Trakerr::ApiClient.new(default_config) @events_api = Trakerr::EventsApi.new(api_client) end ## - #Creates a new AppEvent and returns it with a stacktrace if err is an exception object. - #If passed false, it returns an AppEvent without a stacktrace. - #RETURNS: An AppEvent instance with the default event information. - #err:Exception: The exception that is captured or rescued, or false if you don't need a stacktrace. - #log_level:String: Logging level, currently one of 'debug','info','warning','error', 'fatal', defaults to 'error'. See loglevel in AppEvent for an always current list of values. - #Will argument error if passed another value. - #classification:String: Optional extra descriptor string. Will default to issue if not passed a value. - #eventType:string: String representation of the type of error. - #Defaults to err.class.name if err is an exception, unknown if not. - #eventMessage:String: String representation of the message of the error. - #Defaults to err.message if err is an exception, unknown if not. + # Creates a new AppEvent and returns it with a stacktrace if err is an exception object. + # If passed false, it returns an AppEvent without a stacktrace. + # RETURNS: An AppEvent instance with the default event information. + # err:Exception: The exception that is captured or rescued, or false if you don't need a stacktrace. + # log_level:String: Logging level, currently one of 'debug','info','warning','error', 'fatal', defaults to 'error'. See loglevel in AppEvent for an always current list of values. + # Will argument error if passed another value. + # classification:String: Optional extra descriptor string. Will default to issue if not passed a value. + # eventType:string: String representation of the type of error. + # Defaults to err.class.name if err is an exception, unknown if not. + # eventMessage:String: String representation of the message of the error. + # Defaults to err.message if err is an exception, unknown if not. ## - def CreateAppEvent(err = false, log_level="Error", classification="issue", eventType="unknown", eventMessage="unknown") - raise ArgumentError, "All non err arguments are expected strings." unless (log_level.is_a? String) && (classification.is_a? String) && (eventType.is_a? String) && (eventMessage.is_a? String) + def create_app_event(err = false, log_level = 'Error', classification = 'issue', eventType = 'unknown', eventMessage = 'unknown') + raise ArgumentError, 'All non err arguments are expected strings.' unless (log_level.is_a? String) && (classification.is_a? String) && (eventType.is_a? String) && (eventMessage.is_a? String) if err != false - raise ArgumentError, "err is expected instance of exception." unless err.is_a? Exception - - eventType = err.class.name if eventType == "unknown" - - eventMessage = err.message if eventMessage == "unknown" + raise ArgumentError, 'err is expected instance of exception.' unless err.is_a? Exception + + eventType = err.class.name if eventType == 'unknown' || eventType == '' + + eventMessage = err.message if eventMessage == 'unknown' || eventMessage == '' end log_level = log_level.downcase - app_event_new = AppEvent.new({classification: classification, eventType: eventType, eventMessage: eventMessage}) + app_event_new = AppEvent.new(classification: classification, eventType: eventType, eventMessage: eventMessage) begin app_event_new.log_level = log_level rescue ArgumentError - app_event_new.log_level = "error" + app_event_new.log_level = 'error' end - + app_event_new.event_stacktrace = EventTraceBuilder.get_stacktrace(err) if err != false - return app_event_new + app_event_new end - + ## - #A single line method to send an event to trakerr. - #Use may it in a begin-rescue and pass in an error, - #or set error to false if you don't need a stacktrace. - #arg_hash takes in a few common values that you may want to populate - #your app event with in a hash. - #arg_hash:Hash: A hash with a key value pair for each of the following elements - #{"user": "...", "session": "...", "evntname": "...", "evntmessage": "..."}. - #Omit any element you don't need to fill in the event. - #If you are NOT sending an error it is recommended that you pass in an evntname and evntmessage. - #Remember that all keys are expected strings, and so it may be safer to you use the arrow - #operator (=>) so you don't forget to add the space. - #error:Exception: The exception you may be sending. Set this to false if you are sending a non-error. - #This throws an Argument error if error is not an Exception and it's child classes or false. - #log_level:String: The string representation of the level of the error. - #classification:String: The string representation on the classification of the issue. + # A single line method to send an event to trakerr. + # Use may it in a begin-rescue and pass in an error, + # or set error to false if you don't need a stacktrace. + # arg_hash takes in a few common values that you may want to populate + # your app event with in a hash. + # arg_hash:Hash: A hash with a key value pair for each of the following elements + # {"user": "...", "session": "...", "evntname": "...", "evntmessage": "..."}. + # Omit any element you don't need to fill in the event. + # If you are NOT sending an error it is recommended that you pass in an evntname and evntmessage. + # Remember that all keys are expected strings, and so it may be safer to you use the arrow + # operator (=>) so you don't forget to add the space. + # error:Exception: The exception you may be sending. Set this to false if you are sending a non-error. + # This throws an Argument error if error is not an Exception and it's child classes or false. + # log_level:String: The string representation of the level of the error. + # classification:String: The string representation on the classification of the issue. ## - def log(arg_hash, error, log_level = "error", classification = "issue") - raise ArgumentError, "arg_hash is expected to be a hash" unless arg_hash.is_a? Hash - raise ArgumentError, "log_level and classification is expected strings." unless (log_level.is_a? String) && (classification.is_a? String) + def log(arg_hash, error, log_level = 'error', classification = 'issue') + raise ArgumentError, 'arg_hash is expected to be a hash' unless arg_hash.is_a? Hash + raise ArgumentError, 'log_level and classification is expected strings.' unless (log_level.is_a? String) && (classification.is_a? String) app_event = nil if error != false - raise ArgumentError, "err is expected instance of exception." unless error.is_a? Exception - app_event = CreateAppEvent(error, log_level, classification, arg_hash.fetch("evntname", "unknown"), arg_hash.fetch("evntmessage", "unknown")) - + raise ArgumentError, 'err is expected instance of exception.' unless error.is_a? Exception + app_event = create_app_event(error, log_level, classification, arg_hash.fetch('eventname', 'unknown'), arg_hash.fetch('evntmessage', 'unknown')) + end - app_event = CreateAppEvent(false,log_level, classification, arg_hash.fetch("evntname", "unknown"), arg_hash.fetch("evntmessage", "unknown")) if app_event.nil? - app_event.event_user = arg_hash["user"] if arg_hash.has_key? "user" - app_event.event_session = arg_hash["session"] if arg_hash.has_key? "session" + app_event = create_app_event(false, log_level, classification, arg_hash.fetch('eventname', 'unknown'), arg_hash.fetch('evntmessage', 'unknown')) if app_event.nil? + app_event.event_user = arg_hash['user'] if arg_hash.key? 'user' + app_event.event_session = arg_hash['session'] if arg_hash.key? 'session' - SendEvent(app_event) + send_event(app_event) end ## - #Sends the given AppEvent to Trakerr - #appEvent:AppEvent: The AppEvent to send. + # Sends the given AppEvent to Trakerr + # appEvent:AppEvent: The AppEvent to send. ## - def SendEvent(appEvent) - @events_api.events_post(FillDefaults(appEvent)) + def send_event(appEvent) + @events_api.events_post(fill_defaults(appEvent)) end ## - #Populates the given AppEvent with the client level default values - #RETURNS: The AppEvent with Defaults filled. - #appEvent:AppEvent: The AppEvent to fill. + # Populates the given AppEvent with the client level default values + # RETURNS: The AppEvent with Defaults filled. + # appEvent:AppEvent: The AppEvent to fill. ## - def FillDefaults(appEvent) - appEvent.api_key = appEvent.api_key || @apiKey + def fill_defaults(appEvent) + appEvent.api_key = appEvent.api_key || @api_key - appEvent.context_app_version = appEvent.context_app_version || @contextAppVersion - appEvent.deployment_stage = appEvent.deployment_stage || @contextDeploymentStage + appEvent.context_app_version = appEvent.context_app_version || @context_app_version + appEvent.deployment_stage = appEvent.deployment_stage || @context_deployment_stage - appEvent.context_env_language = appEvent.context_env_language || @contextEnvLanguage - appEvent.context_env_name = appEvent.context_env_name || @contextEnvName - appEvent.context_env_version = appEvent.context_env_version || @contextEnvVersion - appEvent.context_env_hostname = appEvent.context_env_hostname || @contextEnvHostname + appEvent.context_env_language = appEvent.context_env_language || @context_env_language + appEvent.context_env_name = appEvent.context_env_name || @context_env_name + appEvent.context_env_version = appEvent.context_env_version || @context_env_version + appEvent.context_env_hostname = appEvent.context_env_hostname || @context_env_version - appEvent.context_app_os = appEvent.context_app_os || @contextAppOS - appEvent.context_app_os_version = appEvent.context_app_os_version || @contextAppOSVersion + appEvent.context_app_os = appEvent.context_app_os || @context_app_os + appEvent.context_app_os_version = appEvent.context_app_os_version || @context_app_os_version - appEvent.context_app_browser = appEvent.context_app_browser || @contextAppBrowser - appEvent.context_app_browser_version = appEvent.context_app_browser_version || @contextAppBrowserVersion + appEvent.context_app_browser = appEvent.context_app_browser || @context_app_browser + appEvent.context_app_browser_version = appEvent.context_app_browser_version || @context_app_browser_version - appEvent.context_data_center = appEvent.context_data_center || @contextDataCenter - appEvent.context_data_center_region = appEvent.context_data_center_region || @contextDataCenterRegion + appEvent.context_data_center = appEvent.context_data_center || @context_data_center + appEvent.context_data_center_region = appEvent.context_data_center_region || @context_data_center_region + + appEvent.event_time = DateTime.now.strftime('%Q').to_i + + output = %x(uptime) + + begin + output = %x(uptime) + appEvent.context_cpu_percentage = appEvent.context_cpu_percentage || (output.split(" ")[8].tr!(',','').to_f*100).round - appEvent.event_time = DateTime.now.strftime("%Q").to_i - return appEvent + output = %x(free) + appEvent.context_memory_percentage = appEvent.context_memory_percentage || ((output.split(" ")[8].to_f/output.split(" ")[7].to_f) * 100).round + rescue + #fail silently for all standard error here. + end + + appEvent #return appEvent end private - ## - #Used for parsing large strings. Gets the text in between a prefix string and a suffix string. - #Currently used to parse responses from shell commands on OS. - #RETURNS: The String from text between prefix and suffix or nil if not found or errors occur. - #text:String: The text to search in. - #prefix:String: The prefix string to start getting the text after - #suffix:String: The suffix string to find the ending index for. - ## - def GetTextFromLine(text, prefix, suffix) - raise ArgumentError, "All arguments are expected strings." unless (text.is_a? String) && (prefix.is_a? String) && (suffix.is_a? String) - - prefixindex = text.index(prefix) - return nil if prefixindex == nil - prefixindex = prefixindex + prefix.length - suffixindex = text.index(suffix, prefixindex) - return nil if suffixindex == nil + ## + # Used for parsing large strings. Gets the text in between a prefix string and a suffix string. + # Currently used to parse responses from shell commands on OS. + # RETURNS: The String from text between prefix and suffix or nil if not found or errors occur. + # text:String: The text to search in. + # prefix:String: The prefix string to start getting the text after + # suffix:String: The suffix string to find the ending index for. + ## + def get_text_from_line(text, prefix, suffix) + raise ArgumentError, 'All arguments are expected strings.' unless (text.is_a? String) && (prefix.is_a? String) && (suffix.is_a? String) + + prefixindex = text.index(prefix) + return nil if prefixindex.nil? + prefixindex += prefix.length - text[prefixindex...suffixindex] - end + suffixindex = text.index(suffix, prefixindex) + return nil if suffixindex.nil? + + text[prefixindex...suffixindex] + end end -end \ No newline at end of file +end diff --git a/trakerr/lib/trakerr_formatter.rb b/trakerr/lib/trakerr_formatter.rb new file mode 100644 index 0000000..992e655 --- /dev/null +++ b/trakerr/lib/trakerr_formatter.rb @@ -0,0 +1,14 @@ +require 'logger' + +module Trakerr + class TrakerrFormatter < Logger::Formatter + def initialize() + super() + end + + def call(severity, time, progname, msg) + severityid = severity[0] + "#{severityid}, [#{time}] #{severity} #{progname} : #{msg2str(msg)}\n" + end + end +end diff --git a/trakerr/lib/trakerr_logger.rb b/trakerr/lib/trakerr_logger.rb new file mode 100644 index 0000000..c2643e1 --- /dev/null +++ b/trakerr/lib/trakerr_logger.rb @@ -0,0 +1,94 @@ +require 'logger' +require 'delegate' +require 'event_trace_builder' +require 'trakerr_client' + +module Trakerr + class TrakerrLogger < SimpleDelegator + attr_accessor :min_severity + attr_accessor :trakerr_client + + def initalize(logger) + __setobj__(logger) + @min_severity = Logger::WARN + #@trakerr_client TODO: Finish send to trakerr. + end + + ## + # @see Logger#debug + def debug(progname = nil, &block) + send_to_trakerr(Logger::DEBUG, progname) + super + end + + ## + # @see Logger#info + def info(progname = nil, &block) + send_to_trakerr(Logger::INFO, progname) + super + end + + ## + # @see Logger#warn + def warn(progname = nil, &block) + send_to_trakerr(Logger::WARN, progname) + super + end + + ## + # @see Logger#error + def error(progname = nil, &block) + send_to_trakerr(Logger::ERROR, progname) + super + end + + ## + # @see Logger#fatal + def fatal(progname = nil, &block) + send_to_trakerr(Logger::FATAL, progname) + super + end + + ## + # @see Logger#unknown + def unknown(progname = nil, &block) + send_to_trakerr(Logger::UNKNOWN, progname) + super + end + + private + + def send_to_trakerr(ex) + if ex.is_a?(Exception)#TODO: Work on recognizing the java logger, and it having a backtrace. + ex.set_backtrace(build_backtrace()) unless ex.backtrace + return ex + end + + err = RuntimeError.new(ex.to_s) + err.set_backtrace(build_backtrace()) + err + end + + def build_backtrace() + backtrace = Kernal.caller + backtrace.drop_while { |frame| frame[:file] =~ %r{/logger.rb\z} } + backtrace + end + + def severity_to_string(severity) + case severity + when Logger::DEBUG + "debug" + when Logger::INFO + "info" + when Logger::WARN + "warn" + when Logger::ERROR + "error" + when Logger::Fatal + "fatal" + else + "fatal"#If logging "UNKNOWN" + end + end +end \ No newline at end of file diff --git a/trakerr/lib/trakerr_writer.rb b/trakerr/lib/trakerr_writer.rb new file mode 100644 index 0000000..06f8570 --- /dev/null +++ b/trakerr/lib/trakerr_writer.rb @@ -0,0 +1,85 @@ +require 'trakerr_client' +require 'event_trace_builder' + +module Trakerr + class TrakerrWriter < StringIO + def initialize(api_key, context_app_version = '1.0', context_deployment_stage = 'development', log_limit = 'warn', standard_format = true, parse_function = nil) + super() + @client = Trakerr::TrakerrClient.new(api_key, context_app_version, context_deployment_stage) + + @log_limit = str2level(log_limit) + @standard_format = standard_format + @parse_function = parse_function + end + + def write(str) + strarray = str.dup.split("\n") + loglevel, classification, evname, evmessage = nil + stacktrace = [] + + if @standard_format + loglevel, classification, evname, evmessage, stacktrace = parse_standard(strarray) + else + loglevel, classification, evname, evmessage, stacktrace = @parse_function.call(strarray) + end + + if str2level(loglevel) >= @log_limit + event = @client.create_app_event(false, loglevel, classification, evname, evmessage) + event.event_stacktrace = EventTraceBuilder.get_logger_stacktrace(evname, evmessage, stacktrace) \ + unless stacktrace.empty? + @client.send_event(event) + end + + super(str) + end + + private + def parse_standard(_buffer) + loglevel = nil + classification = nil + evname = nil + evmessage = nil + stacktrace = [] + + _buffer.each_index do |i| + if i == 0 # TrakerrFormatter dictates severity as the first line. + ob = _buffer[i].match(/\S+ \[.*\] (?\w+) (?\w*) : (?[\w\s]+) \((?\w+)\)/i) + loglevel = ob[:level] + classification = ob[:progname] + evname = ob[:name] + evmessage = ob[:message] + + else # All following lines are stacktrace shoved into the buffer automatically if provided. + # This is only given if the logger gets an error object, + # but I don't believe the TrakerrFormatter has access to it to parse out in the RE. + + stacktrace << _buffer[i] + end + end + + [loglevel, classification, evname, evmessage, stacktrace] + end + + def str2level(level) + level.downcase! + level.strip! + + case level + when "unknown" + 50 + when "fatal" + 40 + when "error" + 30 + when "warn", "warning" + 20 + when "info" + 10 + when "debug" + 0 + else + 30 + end + end + end +end diff --git a/trakerr_client.gemspec b/trakerr_client.gemspec index c3309e3..cecc955 100644 --- a/trakerr_client.gemspec +++ b/trakerr_client.gemspec @@ -29,7 +29,7 @@ $:.push File.expand_path("../trakerr/lib", __FILE__) Gem::Specification.new do |s| s.name = "trakerr_client" - s.version = "2.0.0" + s.version = "2.1.0" s.platform = Gem::Platform::RUBY s.authors = ["Trakerr Dev Team", "Swagger-Codegen"] s.email = [""] @@ -51,7 +51,7 @@ Gem::Specification.new do |s| s.add_development_dependency 'autotest-fsevent', '~> 0.2', '>= 0.2.11' #s.files = `find *`.split("\n").uniq.sort.select{|f| !f.empty? } - #REQUIRES GIT INSTALLED + #REQUIRES GIT INSTALLED, works on windows. s.files = `git ls-files`.split("\n").delete_if {|file| file.include? "spec"} s.test_files = `git ls-files`.split("\n").delete_if {|file| not file.include? "spec"} s.executables = []