hoodoo 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. checksums.yaml +7 -0
  2. data/bin/hoodoo +5 -0
  3. data/lib/hoodoo.rb +27 -0
  4. data/lib/hoodoo/active.rb +32 -0
  5. data/lib/hoodoo/active/active_model/uuid_validator.rb +45 -0
  6. data/lib/hoodoo/active/active_record/base.rb +81 -0
  7. data/lib/hoodoo/active/active_record/creator.rb +134 -0
  8. data/lib/hoodoo/active/active_record/dated.rb +343 -0
  9. data/lib/hoodoo/active/active_record/error_mapping.rb +351 -0
  10. data/lib/hoodoo/active/active_record/finder.rb +606 -0
  11. data/lib/hoodoo/active/active_record/search_helper.rb +189 -0
  12. data/lib/hoodoo/active/active_record/secure.rb +431 -0
  13. data/lib/hoodoo/active/active_record/support.rb +106 -0
  14. data/lib/hoodoo/active/active_record/translated.rb +87 -0
  15. data/lib/hoodoo/active/active_record/uuid.rb +80 -0
  16. data/lib/hoodoo/active/active_record/writer.rb +321 -0
  17. data/lib/hoodoo/client.rb +23 -0
  18. data/lib/hoodoo/client/augmented_array.rb +29 -0
  19. data/lib/hoodoo/client/augmented_base.rb +168 -0
  20. data/lib/hoodoo/client/augmented_hash.rb +23 -0
  21. data/lib/hoodoo/client/client.rb +354 -0
  22. data/lib/hoodoo/client/endpoint/endpoint.rb +427 -0
  23. data/lib/hoodoo/client/endpoint/endpoints/amqp.rb +180 -0
  24. data/lib/hoodoo/client/endpoint/endpoints/auto_session.rb +194 -0
  25. data/lib/hoodoo/client/endpoint/endpoints/http.rb +203 -0
  26. data/lib/hoodoo/client/endpoint/endpoints/http_based.rb +367 -0
  27. data/lib/hoodoo/client/endpoint/endpoints/not_found.rb +59 -0
  28. data/lib/hoodoo/client/headers.rb +269 -0
  29. data/lib/hoodoo/communicators.rb +23 -0
  30. data/lib/hoodoo/communicators/fast.rb +44 -0
  31. data/lib/hoodoo/communicators/pool.rb +601 -0
  32. data/lib/hoodoo/communicators/slow.rb +84 -0
  33. data/lib/hoodoo/data.rb +51 -0
  34. data/lib/hoodoo/data/resources/caller.rb +39 -0
  35. data/lib/hoodoo/data/resources/errors.rb +28 -0
  36. data/lib/hoodoo/data/resources/log.rb +31 -0
  37. data/lib/hoodoo/data/resources/session.rb +26 -0
  38. data/lib/hoodoo/data/types/error_primitive.rb +27 -0
  39. data/lib/hoodoo/data/types/permissions.rb +40 -0
  40. data/lib/hoodoo/data/types/permissions_defaults.rb +32 -0
  41. data/lib/hoodoo/data/types/permissions_full.rb +28 -0
  42. data/lib/hoodoo/data/types/permissions_resources.rb +31 -0
  43. data/lib/hoodoo/discovery.rb +20 -0
  44. data/lib/hoodoo/errors.rb +19 -0
  45. data/lib/hoodoo/errors/error_descriptions.rb +229 -0
  46. data/lib/hoodoo/errors/errors.rb +322 -0
  47. data/lib/hoodoo/generator.rb +139 -0
  48. data/lib/hoodoo/logger.rb +23 -0
  49. data/lib/hoodoo/logger/fast_writer.rb +27 -0
  50. data/lib/hoodoo/logger/flattener_mixin.rb +36 -0
  51. data/lib/hoodoo/logger/logger.rb +387 -0
  52. data/lib/hoodoo/logger/slow_writer.rb +49 -0
  53. data/lib/hoodoo/logger/writer_mixin.rb +52 -0
  54. data/lib/hoodoo/logger/writers/file_writer.rb +45 -0
  55. data/lib/hoodoo/logger/writers/log_entries_dot_com_writer.rb +64 -0
  56. data/lib/hoodoo/logger/writers/stream_writer.rb +43 -0
  57. data/lib/hoodoo/middleware.rb +33 -0
  58. data/lib/hoodoo/presenters.rb +45 -0
  59. data/lib/hoodoo/presenters/base.rb +281 -0
  60. data/lib/hoodoo/presenters/base_dsl.rb +519 -0
  61. data/lib/hoodoo/presenters/common_resource_fields.rb +31 -0
  62. data/lib/hoodoo/presenters/embedding.rb +232 -0
  63. data/lib/hoodoo/presenters/types/array.rb +118 -0
  64. data/lib/hoodoo/presenters/types/boolean.rb +26 -0
  65. data/lib/hoodoo/presenters/types/date.rb +26 -0
  66. data/lib/hoodoo/presenters/types/date_time.rb +26 -0
  67. data/lib/hoodoo/presenters/types/decimal.rb +47 -0
  68. data/lib/hoodoo/presenters/types/enum.rb +55 -0
  69. data/lib/hoodoo/presenters/types/field.rb +158 -0
  70. data/lib/hoodoo/presenters/types/float.rb +26 -0
  71. data/lib/hoodoo/presenters/types/hash.rb +361 -0
  72. data/lib/hoodoo/presenters/types/integer.rb +26 -0
  73. data/lib/hoodoo/presenters/types/object.rb +117 -0
  74. data/lib/hoodoo/presenters/types/string.rb +53 -0
  75. data/lib/hoodoo/presenters/types/tags.rb +24 -0
  76. data/lib/hoodoo/presenters/types/text.rb +26 -0
  77. data/lib/hoodoo/presenters/types/uuid.rb +54 -0
  78. data/lib/hoodoo/services.rb +34 -0
  79. data/lib/hoodoo/services/discovery/discoverers/by_consul.rb +66 -0
  80. data/lib/hoodoo/services/discovery/discoverers/by_convention.rb +173 -0
  81. data/lib/hoodoo/services/discovery/discoverers/by_drb/by_drb.rb +195 -0
  82. data/lib/hoodoo/services/discovery/discoverers/by_drb/drb_server.rb +166 -0
  83. data/lib/hoodoo/services/discovery/discoverers/by_drb/drb_server_start.rb +37 -0
  84. data/lib/hoodoo/services/discovery/discovery.rb +186 -0
  85. data/lib/hoodoo/services/discovery/results/for_amqp.rb +58 -0
  86. data/lib/hoodoo/services/discovery/results/for_http.rb +85 -0
  87. data/lib/hoodoo/services/discovery/results/for_local.rb +85 -0
  88. data/lib/hoodoo/services/discovery/results/for_remote.rb +57 -0
  89. data/lib/hoodoo/services/middleware/amqp_log_message.rb +186 -0
  90. data/lib/hoodoo/services/middleware/amqp_log_writer.rb +119 -0
  91. data/lib/hoodoo/services/middleware/endpoints/inter_resource_local.rb +130 -0
  92. data/lib/hoodoo/services/middleware/endpoints/inter_resource_remote.rb +202 -0
  93. data/lib/hoodoo/services/middleware/exception_reporting/base_reporter.rb +105 -0
  94. data/lib/hoodoo/services/middleware/exception_reporting/exception_reporting.rb +115 -0
  95. data/lib/hoodoo/services/middleware/exception_reporting/reporters/airbrake_reporter.rb +64 -0
  96. data/lib/hoodoo/services/middleware/exception_reporting/reporters/raygun_reporter.rb +63 -0
  97. data/lib/hoodoo/services/middleware/interaction.rb +127 -0
  98. data/lib/hoodoo/services/middleware/middleware.rb +2705 -0
  99. data/lib/hoodoo/services/middleware/rack_monkey_patch.rb +73 -0
  100. data/lib/hoodoo/services/services/context.rb +153 -0
  101. data/lib/hoodoo/services/services/implementation.rb +132 -0
  102. data/lib/hoodoo/services/services/interface.rb +934 -0
  103. data/lib/hoodoo/services/services/permissions.rb +250 -0
  104. data/lib/hoodoo/services/services/request.rb +189 -0
  105. data/lib/hoodoo/services/services/response.rb +316 -0
  106. data/lib/hoodoo/services/services/service.rb +141 -0
  107. data/lib/hoodoo/services/services/session.rb +729 -0
  108. data/lib/hoodoo/utilities.rb +12 -0
  109. data/lib/hoodoo/utilities/string_inquirer.rb +54 -0
  110. data/lib/hoodoo/utilities/utilities.rb +380 -0
  111. data/lib/hoodoo/utilities/uuid.rb +44 -0
  112. data/lib/hoodoo/version.rb +17 -0
  113. data/spec/active/active_record/base_spec.rb +57 -0
  114. data/spec/active/active_record/creator_spec.rb +88 -0
  115. data/spec/active/active_record/dated_spec.rb +248 -0
  116. data/spec/active/active_record/error_mapping_spec.rb +360 -0
  117. data/spec/active/active_record/finder_spec.rb +744 -0
  118. data/spec/active/active_record/search_helper_spec.rb +384 -0
  119. data/spec/active/active_record/secure_spec.rb +435 -0
  120. data/spec/active/active_record/support_spec.rb +225 -0
  121. data/spec/active/active_record/translated_spec.rb +19 -0
  122. data/spec/active/active_record/uuid_spec.rb +72 -0
  123. data/spec/active/active_record/writer_spec.rb +272 -0
  124. data/spec/alchemy/alchemy-amq.rb +33 -0
  125. data/spec/client/augmented_array_spec.rb +15 -0
  126. data/spec/client/augmented_base_spec.rb +50 -0
  127. data/spec/client/augmented_hash_spec.rb +15 -0
  128. data/spec/client/client_spec.rb +955 -0
  129. data/spec/client/endpoint/endpoint_spec.rb +70 -0
  130. data/spec/client/endpoint/endpoints/amqp_spec.rb +16 -0
  131. data/spec/client/endpoint/endpoints/auto_session_spec.rb +9 -0
  132. data/spec/client/endpoint/endpoints/http_based_spec.rb +9 -0
  133. data/spec/client/endpoint/endpoints/http_spec.rb +103 -0
  134. data/spec/client/endpoint/endpoints/not_found_spec.rb +35 -0
  135. data/spec/client/headers_spec.rb +172 -0
  136. data/spec/communicators/fast_spec.rb +9 -0
  137. data/spec/communicators/pool_spec.rb +339 -0
  138. data/spec/communicators/slow_spec.rb +15 -0
  139. data/spec/data/resources/caller_spec.rb +156 -0
  140. data/spec/data/resources/errors_spec.rb +22 -0
  141. data/spec/data/resources/log_spec.rb +20 -0
  142. data/spec/data/resources/session_spec.rb +15 -0
  143. data/spec/data/types/error_primitive_spec.rb +15 -0
  144. data/spec/data/types/permissions_defaults_spec.rb +25 -0
  145. data/spec/data/types/permissions_full_spec.rb +44 -0
  146. data/spec/data/types/permissions_resources_spec.rb +34 -0
  147. data/spec/data/types/permissions_spec.rb +37 -0
  148. data/spec/errors/error_descriptions_spec.rb +98 -0
  149. data/spec/errors/errors_spec.rb +346 -0
  150. data/spec/integration/service_actions_spec.rb +112 -0
  151. data/spec/logger/fast_writer_spec.rb +18 -0
  152. data/spec/logger/logger_spec.rb +259 -0
  153. data/spec/logger/slow_writer_spec.rb +144 -0
  154. data/spec/logger/writers/file_writer_spec.rb +37 -0
  155. data/spec/logger/writers/log_entries_dot_com_writer_spec.rb +29 -0
  156. data/spec/logger/writers/stream_writer_spec.rb +38 -0
  157. data/spec/presenters/base_dsl_spec.rb +111 -0
  158. data/spec/presenters/base_spec.rb +871 -0
  159. data/spec/presenters/common_resource_fields_spec.rb +30 -0
  160. data/spec/presenters/embedding_spec.rb +87 -0
  161. data/spec/presenters/types/array_spec.rb +249 -0
  162. data/spec/presenters/types/boolean_spec.rb +51 -0
  163. data/spec/presenters/types/date_spec.rb +57 -0
  164. data/spec/presenters/types/date_time_spec.rb +59 -0
  165. data/spec/presenters/types/decimal_spec.rb +58 -0
  166. data/spec/presenters/types/enum_spec.rb +71 -0
  167. data/spec/presenters/types/field_spec.rb +77 -0
  168. data/spec/presenters/types/float_spec.rb +50 -0
  169. data/spec/presenters/types/hash_spec.rb +1069 -0
  170. data/spec/presenters/types/integer_spec.rb +50 -0
  171. data/spec/presenters/types/object_spec.rb +177 -0
  172. data/spec/presenters/types/string_spec.rb +65 -0
  173. data/spec/presenters/types/tags_spec.rb +56 -0
  174. data/spec/presenters/types/text_spec.rb +50 -0
  175. data/spec/presenters/types/uuid_spec.rb +46 -0
  176. data/spec/presenters/walk_spec.rb +198 -0
  177. data/spec/services/discovery/discoverers/by_consul_spec.rb +29 -0
  178. data/spec/services/discovery/discoverers/by_convention_spec.rb +67 -0
  179. data/spec/services/discovery/discoverers/by_drb/by_drb_spec.rb +80 -0
  180. data/spec/services/discovery/discoverers/by_drb/drb_server_spec.rb +205 -0
  181. data/spec/services/discovery/discovery_spec.rb +73 -0
  182. data/spec/services/discovery/results/for_amqp_spec.rb +17 -0
  183. data/spec/services/discovery/results/for_http_spec.rb +37 -0
  184. data/spec/services/discovery/results/for_local_spec.rb +21 -0
  185. data/spec/services/discovery/results/for_remote_spec.rb +15 -0
  186. data/spec/services/middleware/amqp_log_message_spec.rb +60 -0
  187. data/spec/services/middleware/amqp_log_writer_spec.rb +95 -0
  188. data/spec/services/middleware/endpoints/inter_resource_local_spec.rb +9 -0
  189. data/spec/services/middleware/endpoints/inter_resource_remote_spec.rb +9 -0
  190. data/spec/services/middleware/exception_reporting/base_reporter_spec.rb +16 -0
  191. data/spec/services/middleware/exception_reporting/exception_reporting_spec.rb +92 -0
  192. data/spec/services/middleware/exception_reporting/reporters/airbrake_reporter_spec.rb +24 -0
  193. data/spec/services/middleware/exception_reporting/reporters/raygun_reporter_spec.rb +23 -0
  194. data/spec/services/middleware/middleware_cors_spec.rb +93 -0
  195. data/spec/services/middleware/middleware_create_update_spec.rb +489 -0
  196. data/spec/services/middleware/middleware_dated_at_spec.rb +186 -0
  197. data/spec/services/middleware/middleware_exotic_communication_spec.rb +560 -0
  198. data/spec/services/middleware/middleware_logging_spec.rb +356 -0
  199. data/spec/services/middleware/middleware_multi_local_spec.rb +1094 -0
  200. data/spec/services/middleware/middleware_multi_remote_spec.rb +1440 -0
  201. data/spec/services/middleware/middleware_permissions_spec.rb +1014 -0
  202. data/spec/services/middleware/middleware_public_spec.rb +238 -0
  203. data/spec/services/middleware/middleware_spec.rb +1569 -0
  204. data/spec/services/middleware/string_inquirer_spec.rb +30 -0
  205. data/spec/services/services/application_spec.rb +74 -0
  206. data/spec/services/services/context_spec.rb +48 -0
  207. data/spec/services/services/implementation_spec.rb +45 -0
  208. data/spec/services/services/interface_spec.rb +262 -0
  209. data/spec/services/services/permissions_spec.rb +249 -0
  210. data/spec/services/services/request_spec.rb +95 -0
  211. data/spec/services/services/response_spec.rb +250 -0
  212. data/spec/services/services/session_spec.rb +432 -0
  213. data/spec/spec_helper.rb +298 -0
  214. data/spec/utilities/utilities_spec.rb +537 -0
  215. data/spec/utilities/uuid_spec.rb +20 -0
  216. metadata +615 -0
@@ -0,0 +1,139 @@
1
+ ########################################################################
2
+ # File:: generator.rb
3
+ # (C):: Loyalty New Zealand 2014
4
+ #
5
+ # Purpose:: Implement the +hoodoo+ command line interface. See also
6
+ # +/bin/hoodoo+.
7
+ # ----------------------------------------------------------------------
8
+ # 07-Oct-2014 (JDC): Created.
9
+ ########################################################################
10
+
11
+ require 'singleton'
12
+ require 'fileutils'
13
+
14
+ module Hoodoo
15
+
16
+ # Implement the +hoodoo+ command line interface.
17
+ #
18
+ class Generator
19
+ include Singleton
20
+
21
+ # Kernel::exit takes a boolean but defines no constants to describe
22
+ # what it means; very bad form. This constant equates to the 'success'
23
+ # boolean value.
24
+ #
25
+ KERNEL_EXIT_SUCCESS = true
26
+
27
+ # Kernel::exit takes a boolean but defines no constants to describe
28
+ # what it means; very bad form. This constant equates to the 'failed'
29
+ # boolean value.
30
+ #
31
+ KERNEL_EXIT_FAILURE = false
32
+
33
+ # Regular expression describing allowed names of services (A-Z,
34
+ # a-z, 0-9, underscore or hyphen; between 2 and 30 characters).
35
+ #
36
+ NAME_REGEX = /^[a-zA-Z01-9_-]{2,30}$/
37
+
38
+ # Run the +hoodoo+ command implementation.
39
+ #
40
+ # +args+:: Array of command line arguments, excluding the +hoodoo+
41
+ # command itself (so, just any extra arguments passed in).
42
+ #
43
+ def run!( args )
44
+ return show_usage if args_empty?(args)
45
+
46
+ name = args.first
47
+
48
+ return show_usage if name == '-h' || name == '--help'
49
+
50
+ return usage_and_warning( "SERVICE_NAME must match #{ NAME_REGEX.inspect }" ) if naughty_name?( name )
51
+ return usage_and_warning( "'#{ name }' already exists" ) if File.exist?( "./#{ name }" )
52
+
53
+ git = args[ 2 ] if args[ 1 ] == '--from'
54
+ git ||= 'git@github.com:LoyaltyNZ/service_shell.git'
55
+
56
+ return create_service( name, git )
57
+ end
58
+
59
+ private
60
+
61
+ def create_service( name, git )
62
+ if create_dir( name ) &&
63
+ clone_service_shell( name, git ) &&
64
+ remove_dot_git( name, git ) &&
65
+ replace_strings( name )
66
+
67
+ puts "Success! ./#{name} created."
68
+ Kernel::exit( KERNEL_EXIT_SUCCESS )
69
+ else
70
+ Kernel::exit( KERNEL_EXIT_FAILURE )
71
+ end
72
+ end
73
+
74
+ def create_dir( name )
75
+ `mkdir #{ name }`
76
+ $?.to_i == 0
77
+ end
78
+
79
+ def clone_service_shell( name, git )
80
+ `git clone #{ git } #{ name }`
81
+ $?.to_i == 0
82
+ end
83
+
84
+ def remove_dot_git( name, git )
85
+ git_folder = "./#{ name }/.git"
86
+ git_config = "#{ git_folder }/config"
87
+
88
+ if File.read( git_config ).include?( "url = #{ git }" ) # Paranoid
89
+ FileUtils.remove_dir( git_folder )
90
+ return true
91
+ else
92
+ raise 'Did not find a .git folder with a service_shell config file in it!'
93
+ end
94
+ end
95
+
96
+ def replace_strings( name )
97
+ human_name = name.split( '_' )
98
+ human_name = human_name.drop( 1 ) if ( human_name[ 0 ].downcase == 'service' )
99
+ human_name = human_name.map( &:capitalize ).join( ' ' )
100
+
101
+ base_cmd = "LC_CTYPE=C && LANG=C && find #{ name } -type f -print0 | xargs -0 sed -i \"\" \"s/%s/g\""
102
+ uscore_cmd = base_cmd % "service_shell/#{ Regexp.escape( name ) }"
103
+ human_cmd = base_cmd % "#{ Regexp.escape( 'Platform Service: Generic' ) }/#{ Regexp.escape( 'Platform Service: ' + human_name ) }"
104
+
105
+ puts "Replacing shell names with real service name:"
106
+ puts uscore_cmd
107
+ `#{ uscore_cmd }`
108
+ result = $?.to_i == 0
109
+ return false unless result == true
110
+
111
+ puts human_cmd
112
+ `#{ human_cmd }`
113
+ result = $?.to_i == 0
114
+ return result
115
+ end
116
+
117
+ def args_empty?( args )
118
+ args.empty? || args.first == ''
119
+ end
120
+
121
+ def naughty_name?( name )
122
+ !( name =~ NAME_REGEX )
123
+ end
124
+
125
+ def show_usage
126
+ puts "Usage: hoodoo SERVICE_NAME [--from <git-repository>]"
127
+ puts " e.g. hoodoo service_cron"
128
+ puts " hoodoo service_person --from git@github.com:YOURNAME/service_shell_fork.git"
129
+
130
+ Kernel::exit( KERNEL_EXIT_FAILURE )
131
+ end
132
+
133
+ def usage_and_warning( warning )
134
+ puts "WARNING: #{warning}"
135
+ puts
136
+ show_usage
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,23 @@
1
+ ########################################################################
2
+ # File:: logger.rb
3
+ # (C):: Loyalty New Zealand 2014
4
+ #
5
+ # Purpose:: Include the schema based data validation and rendering code.
6
+ # ----------------------------------------------------------------------
7
+ # 26-Jan-2015 (ADH): Split from top-level inclusion file.
8
+ ########################################################################
9
+
10
+ # Dependencies
11
+
12
+ require 'hoodoo/communicators'
13
+
14
+ # Logger code
15
+
16
+ require 'hoodoo/logger/logger'
17
+ require 'hoodoo/logger/writer_mixin'
18
+ require 'hoodoo/logger/flattener_mixin'
19
+ require 'hoodoo/logger/fast_writer'
20
+ require 'hoodoo/logger/slow_writer'
21
+ require 'hoodoo/logger/writers/file_writer'
22
+ require 'hoodoo/logger/writers/stream_writer'
23
+ require 'hoodoo/logger/writers/log_entries_dot_com_writer'
@@ -0,0 +1,27 @@
1
+ ########################################################################
2
+ # File:: fast_writer.rb
3
+ # (C):: Loyalty New Zealand 2014
4
+ #
5
+ # Purpose:: Base class for fast log writers.
6
+ # ----------------------------------------------------------------------
7
+ # 16-Dec-2014 (ADH): Created.
8
+ ########################################################################
9
+
10
+ module Hoodoo
11
+ class Logger
12
+
13
+ # Log writer classes are used through the Hoodoo::Logger class.
14
+ #
15
+ # Subclass FastWriter if you are writing a log data output mechanism which
16
+ # responds very quickly. File output might fall into this category
17
+ # depending upon target deployment infrastructure; printing to the console
18
+ # would certainly qualify.
19
+ #
20
+ # The subclass only needs to implement
21
+ # Hoodoo::Logger::WriterMixin#report.
22
+ #
23
+ class FastWriter
24
+ include Hoodoo::Logger::WriterMixin
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,36 @@
1
+ ########################################################################
2
+ # File:: flattener_mixin.rb
3
+ # (C):: Loyalty New Zealand 2014
4
+ #
5
+ # Purpose:: Common code for fast and slow log writer base classes.
6
+ # ----------------------------------------------------------------------
7
+ # 19-Dec-2014 (ADH): Created.
8
+ ########################################################################
9
+
10
+ require 'time'
11
+
12
+ module Hoodoo
13
+ class Logger
14
+
15
+ # This mixin is used by custom logger subclasses and defines a single
16
+ # method, Hoodoo::Logger::FlattenerMixin#flatten, which takes
17
+ # structured log data and turns it into a single line of output
18
+ # including ISO 8601 time and date in local server time zone (since
19
+ # that makes log analysis easier for humans working in non-UTC time
20
+ # zone regions).
21
+ #
22
+ # Using this is of course entirely optional, but if you do use it, you
23
+ # will be ensuring consistent non-structured log output across any
24
+ # non-structured writers.
25
+ #
26
+ module FlattenerMixin
27
+
28
+ # Take the parameters from Hoodoo::Logger::WriterMixin#report and
29
+ # return a single line string representing the "flattened" log data.
30
+ #
31
+ def flatten( log_level, component, code, data )
32
+ "#{ log_level.to_s.upcase } [#{ Time.now.iso8601( 6 ) }] #{ component } - #{ code }: #{ data.inspect }"
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,387 @@
1
+ ########################################################################
2
+ # File:: logger.rb
3
+ # (C):: Loyalty New Zealand 2014
4
+ #
5
+ # Purpose:: Multiple output logging via local code or external services.
6
+ # ----------------------------------------------------------------------
7
+ # 16-Dec-2014 (ADH): Created.
8
+ ########################################################################
9
+
10
+ module Hoodoo
11
+
12
+ # Multiple output logging via local code or external services. Instantiate
13
+ # a new Logger, then use #add to add _instances_ of writer classes to the
14
+ # collection of log writers. When #report, #debug, #info, #warn or #error
15
+ # are called, a corresponding log message is sent once to each of the
16
+ # writers, provided that the configured logging level (#level, #level=)
17
+ # allows it.
18
+ #
19
+ # By default, a new logger instance has no configured writers so logged
20
+ # messages will not go anywhere. You must use #add to add at least one
21
+ # writer for the instance to be useful.
22
+ #
23
+ # Some writer classes are provided by Hoodoo, including:
24
+ #
25
+ # * Hoodoo::Logger::StreamWriter - write to output streams, typically
26
+ # expected to be fast, e.g. unredirected $stdout or $stderr.
27
+ #
28
+ # * Hoodoo::Logger::FileWriter - write to files, typically expected to
29
+ # be relatively slow.
30
+ #
31
+ # Some loggers can preserve structural logged data (see #report) while others
32
+ # flatten all log messages. For example, Hoodoo::Logger::StreamWriter must
33
+ # flatten messages but a custom writer that, say, persisted messages in a
34
+ # database should be able to preserve structure.
35
+ #
36
+ # Writers are either considered fast or slow. Fast writers are called inline
37
+ # as soon as a message gets logged. Slow writers are called asynchronously
38
+ # via a Thread. A Queue is used to buffer messages for slow writers; if this
39
+ # gets too large, messages may be dropped. Once the slow writer catches up,
40
+ # a +warn+ level log message is automatically logged to report the number of
41
+ # dropped messages in the interim.
42
+ #
43
+ # To create a new custom writer class of any name/namespace, just subclass
44
+ # Hoodoo::Logger::FastWriter or Hoodoo::Logger::SlowWriter - see those
45
+ # classes for details.
46
+ #
47
+ class Logger
48
+
49
+ # Create a new logger instance. Once created, use #add to add writers.
50
+ #
51
+ # +component+:: Flat logging methods (see #debug, #info, #warn and #error)
52
+ # are internally logged through the structured logger (see
53
+ # #report) using the +component+ (again, see #report)
54
+ # optionally passed here as a Symbol or String. Default is
55
+ # +:Middleware+.
56
+ #
57
+ def initialize( component = :Middleware )
58
+ @level = :debug
59
+ @pool = Hoodoo::Communicators::Pool.new
60
+ @component = component
61
+ @writers = {}
62
+ end
63
+
64
+ # Add a new writer instance to this logger. Example:
65
+ #
66
+ # file_writer = Hoodoo::Logger::FileWriter.new( 'output.log' )
67
+ # stdout_writer = Hoodoo::Logger::StreamWriter.new
68
+ #
69
+ # @logger = Hoodoo::Logger.new
70
+ #
71
+ # logger.add( file_writer )
72
+ # logger.add( stdout_writer )
73
+ #
74
+ # # ...then later...
75
+ #
76
+ # logger.report( ... ) # -> Sends to "output.log" and $stdout
77
+ #
78
+ # +writer_instances+:: One or more _instances_ of a subclass of
79
+ # Hoodoo::Logger::FastWriter or
80
+ # Hoodoo::Logger::SlowWriter, passed as one or
81
+ # more comma-separated parameters.
82
+ #
83
+ def add( *writer_instances )
84
+ writer_instances.each do | writer_instance |
85
+ communicator = if writer_instance.is_a?( Hoodoo::Logger::FastWriter )
86
+ FastCommunicator.new( writer_instance, self )
87
+ elsif writer_instance.is_a?( Hoodoo::Logger::SlowWriter )
88
+ SlowCommunicator.new( writer_instance, self )
89
+ else
90
+ raise "Hoodoo::Logger\#add: Only instances of Hoodoo::Logger::FastWriter or Hoodoo::Logger::SlowWriter can be added - #{ writer_instance.class.name } was given"
91
+ end
92
+
93
+ @pool.add( communicator )
94
+ @writers[ writer_instance ] = communicator
95
+ end
96
+ end
97
+
98
+ # Remove a writer instance from this logger. If the instance has not been
99
+ # previously added, no error is raised.
100
+ #
101
+ # Slow writers may take a while to finish processing and shut down in
102
+ # the background. As a result, this method might take a while to return.
103
+ # Internal default timeouts may even mean that the writer is still running
104
+ # (possibly entirely hung).
105
+ #
106
+ # +writer_instance+:: An _instance_ of a subclass of
107
+ # Hoodoo::Logger::FastWriter or
108
+ # Hoodoo::Logger::SlowWriter.
109
+ #
110
+ def remove( writer_instance )
111
+ communicator = @writers[ writer_instance ]
112
+ @pool.remove( communicator ) unless communicator.nil?
113
+ end
114
+
115
+ # Remove all writer instances from this logger.
116
+ #
117
+ # Slow writers may take a while to finish processing and shut down in
118
+ # the background. As a result, this method might take a while to return.
119
+ # Internal default timeouts may even mean that one or more slow writers
120
+ # are still running (possibly entirely hung).
121
+ #
122
+ def remove_all
123
+ @pool.terminate()
124
+ @writers = {}
125
+ end
126
+
127
+ # Returns an array of all log writer instances currently in use, in order
128
+ # of addition. See #add.
129
+ #
130
+ def instances
131
+
132
+ # Implicit ordering relies on Ruby >= 1.9 documented behaviour of
133
+ # preserving order of addition to a Hash.
134
+ #
135
+ @writers.keys
136
+
137
+ end
138
+
139
+ # Wait for all writers to finish writing all log messages sent up to the
140
+ # point of calling. Internal default timeouts for slow writers mean that
141
+ # hung or extremely slow/backlogged writers may not have finished by the
142
+ # time the call returns, but it's necessary to enforce a timeout else
143
+ # this call may never return at all.
144
+ #
145
+ def wait
146
+ @pool.wait()
147
+ end
148
+
149
+ # Return or set the current log level. This is +:debug+ by default.
150
+ #
151
+ attr_accessor :level
152
+
153
+ # Given the log level configuration of this instance - see #level= and
154
+ # #level - should a message of the given log level be reported? Returns
155
+ # +true+ if so else +false+.
156
+ #
157
+ # This is mostly for internal use but external callers might find it
158
+ # useful from time to time, especially in tests.
159
+ #
160
+ # +log_level+:: Log level of interest as a Symbol - +debug+, +info+, +warn+
161
+ # or +error+.
162
+ #
163
+ def report?( log_level )
164
+ return false if log_level == :debug && @level != :debug
165
+ return false if log_level == :info && @level != :debug && @level != :info
166
+ return false if log_level == :warn && @level != :debug && @level != :info && @level != :warn
167
+ return true
168
+ end
169
+
170
+ # Logs a message using the structured logger. Whether or not log data is
171
+ # written in a stuctured manner depends upon the writer(s) in use (see
172
+ # #add). Structured writers preserve data structures like hashes or arrays
173
+ # rather than (say) dumping things out as strings into flat output streams.
174
+ #
175
+ # As with flat logging methods #debug, #info, #warn and #error, a
176
+ # message is only logged if the logging threshold level (see #level=) is
177
+ # set to an equal or lower level.
178
+ #
179
+ # +log_level+:: Log level as a symbol - one of, from most trivial to most
180
+ # severe, +:debug+, +:info+, +:warn+ or +:error+.
181
+ #
182
+ # +component+:: Component; for example, the resource name for a specific
183
+ # resource endpoint implementation, 'Middleware' for Hoodoo
184
+ # middleware itself, or some other name you think is useful.
185
+ # String or Symbol.
186
+ #
187
+ # +code+:: Component-defined code. Think of this in a manner similar
188
+ # to platform error codes, appearing after the "."; messages
189
+ # related to the same thing should share the same code. The
190
+ # intent is to produce log data that someone can filter on
191
+ # code to get useful information about that specific aspect
192
+ # of a service implementation's behaviour.
193
+ #
194
+ # +data+:: A Hash containing the level-, component- and code-dependent
195
+ # payload data to be logged.
196
+ #
197
+ def report( log_level, component, code, data )
198
+ return unless self.report?( log_level )
199
+
200
+ @pool.communicate(
201
+ Payload.new(
202
+ log_level: log_level,
203
+ component: component,
204
+ code: code,
205
+ data: data
206
+ )
207
+ )
208
+ end
209
+
210
+ # Write a +debug+ log message, provided the log level is +:debug+.
211
+ #
212
+ # The logging data is unstructured, but gets passed to #report for
213
+ # structured logging under the component specified in the constructor
214
+ # and code 'log'.
215
+ #
216
+ # Calling #report is recommended over unstructured direct logging.
217
+ #
218
+ # *args:: One or more arguments that will be treated as strings and
219
+ # written in the presented order to the log, each on its own
220
+ # line of output ("\\n" terminated).
221
+ #
222
+ def debug( *args )
223
+ self.report( :debug, @component, :log, { '_data' => args } )
224
+ end
225
+
226
+ # Write an +info+ log message, provided the log level is +:debug+ or
227
+ # +:info+.
228
+ #
229
+ # The logging data is unstructured, but gets passed to #report for
230
+ # structured logging under the component specified in the constructor
231
+ # and code 'log'.
232
+ #
233
+ # Calling #report is recommended over unstructured direct logging.
234
+ #
235
+ # *args:: One or more arguments that will be treated as strings and
236
+ # written in the presented order to the log, each on its own
237
+ # line of output ("\\n" terminated).
238
+ #
239
+ def info( *args )
240
+ self.report( :info, @component, :log, { '_data' => args } )
241
+ end
242
+
243
+ # Write a +warn+ log message, provided the log level is +:debug+,
244
+ # +:info+ or +:warn+.
245
+ #
246
+ # The logging data is unstructured, but gets passed to #report for
247
+ # structured logging under the component specified in the constructor
248
+ # and code 'log'.
249
+ #
250
+ # Calling #report is recommended over unstructured direct logging.
251
+ #
252
+ # *args:: One or more arguments that will be treated as strings and
253
+ # written in the presented order to the log, each on its own
254
+ # line of output ("\\n" terminated).
255
+ #
256
+ def warn( *args )
257
+ self.report( :warn, @component, :log, { '_data' => args } )
258
+ end
259
+
260
+ # Write an +error+ log message, regardless of logging level.
261
+ #
262
+ # The logging data is unstructured, but gets passed to #report for
263
+ # structured logging under the component specified in the constructor
264
+ # and code 'log'.
265
+ #
266
+ # Calling #report is recommended over unstructured direct logging.
267
+ #
268
+ # *args:: One or more arguments that will be treated as strings and
269
+ # written in the presented order to the log, each on its own
270
+ # line of output ("\\n" terminated).
271
+ #
272
+ def error( *args )
273
+ self.report( :error, @component, :log, { '_data' => args } )
274
+ end
275
+
276
+ # Used internally toommunicate details of a log message across the
277
+ # Hoodoo::Communicators::Pool mechanism and through to a log writer.
278
+ # Log writer authors do not need to use this class;
279
+ # Hoodoo::Logger::WriterMixin unpacks it and calls your subclass's
280
+ # #report implementation with individual parameters for you.
281
+ #
282
+ class Payload
283
+
284
+ # Log level - see Hoodoo::Logger#report.
285
+ #
286
+ attr_reader :log_level
287
+
288
+ # Component - see Hoodoo::Logger#report.
289
+ #
290
+ attr_reader :component
291
+
292
+ # Code - see Hoodoo::Logger#report.
293
+ #
294
+ attr_reader :code
295
+
296
+ # Data - see Hoodoo::Logger#report.
297
+ #
298
+ attr_reader :data
299
+
300
+ # Create an instance. Named parameters are:
301
+ #
302
+ # +log_level+:: See Hoodoo::Logger#report.
303
+ # +component+:: See Hoodoo::Logger#report.
304
+ # +code+:: See Hoodoo::Logger#report.
305
+ # +data+:: See Hoodoo::Logger#report.
306
+ #
307
+ def initialize( log_level:, component:, code:, data: )
308
+ @log_level = log_level
309
+ @component = component
310
+ @code = code
311
+ @data = data
312
+ end
313
+ end
314
+
315
+ # Mixin used internally for the FastCommunicator and SlowCommunicator
316
+ # wrappers that hide implementation complexity from log writer subclasses.
317
+ #
318
+ module Communicator
319
+
320
+ # Create an instance of a logging communicator, based on the given
321
+ # log writer and owning logger instance.
322
+ #
323
+ # +writer_instance+:: Hoodoo::Logger::FastWriter or
324
+ # Hoodoo::Logger::SlowWriter subclass instance
325
+ # that will log things when this Communicator
326
+ # asks it to do so.
327
+ #
328
+ # +owning_logger+:: Hoodoo::Logger instance that will be using
329
+ # this communicator instance.
330
+ #
331
+ def initialize( writer_instance, owning_logger )
332
+ @writer_instance = writer_instance
333
+ @owning_logger = owning_logger
334
+ end
335
+
336
+ # Implement Hoodoo::Communicators::Base#communicate for both slow and
337
+ # fast writers. Assumes it will be passed a Hoodoo::Logger::Payload
338
+ # class instance which describes the structured log data to report; also
339
+ # assumes it is only called when the calling logger's configured log
340
+ # level threshold should allow through the level of the log message in
341
+ # question. Calls through to the #report implementation.
342
+ #
343
+ def communicate( payload )
344
+ @writer_instance.report(
345
+ payload.log_level,
346
+ payload.component,
347
+ payload.code,
348
+ payload.data
349
+ )
350
+ end
351
+
352
+ # Implement optional method Hoodoo::Communicators::Slow#dropped on
353
+ # behalf of subclasses. The method turns the 'messages dropped'
354
+ # notification into a log message of +:warn+ level and which it reports
355
+ # internally immediately for the Writer instance only (since different
356
+ # writers have different queues and thus different dropped message
357
+ # warnings), provided that the owning Hoodoo::Logger instance log
358
+ # level lets warnings through.
359
+ #
360
+ def dropped( number )
361
+ if @owning_logger.report?( :warn )
362
+ @writer_instance.report(
363
+ :warn,
364
+ self.class.name,
365
+ 'dropped.messages',
366
+ "Logging flooded - #{ number } messages dropped"
367
+ )
368
+ end
369
+ end
370
+ end
371
+
372
+ # Used internally as a Hoodoo::Communicator::Pool communicator wrapping
373
+ # fast log writer instances.
374
+ #
375
+ class FastCommunicator < Hoodoo::Communicators::Fast
376
+ include Communicator
377
+ end
378
+
379
+ # Used internally as a Hoodoo::Communicator::Pool communicator wrapping
380
+ # slow log writer instances.
381
+ #
382
+ class SlowCommunicator < Hoodoo::Communicators::Slow
383
+ include Communicator
384
+ end
385
+
386
+ end
387
+ end