fluentd 1.12.1-x64-mingw32 → 1.13.1-x64-mingw32

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of fluentd might be problematic. Click here for more details.

Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/.deepsource.toml +13 -0
  3. data/.github/ISSUE_TEMPLATE/config.yml +2 -2
  4. data/.github/workflows/linux-test.yaml +36 -0
  5. data/.github/workflows/{build.yaml → macos-test.yaml} +8 -7
  6. data/.github/workflows/windows-test.yaml +46 -0
  7. data/.gitlab-ci.yml +19 -19
  8. data/CHANGELOG.md +163 -0
  9. data/CONTRIBUTING.md +2 -2
  10. data/MAINTAINERS.md +5 -2
  11. data/README.md +6 -3
  12. data/example/counter.conf +1 -1
  13. data/fluentd.gemspec +4 -2
  14. data/lib/fluent/command/bundler_injection.rb +1 -1
  15. data/lib/fluent/command/cat.rb +19 -4
  16. data/lib/fluent/command/fluentd.rb +1 -2
  17. data/lib/fluent/command/plugin_config_formatter.rb +2 -1
  18. data/lib/fluent/command/plugin_generator.rb +31 -1
  19. data/lib/fluent/compat/parser.rb +2 -2
  20. data/lib/fluent/config/section.rb +6 -1
  21. data/lib/fluent/config/types.rb +2 -2
  22. data/lib/fluent/event.rb +3 -13
  23. data/lib/fluent/load.rb +0 -1
  24. data/lib/fluent/log.rb +1 -0
  25. data/lib/fluent/plugin/file_wrapper.rb +49 -4
  26. data/lib/fluent/plugin/formatter_ltsv.rb +2 -2
  27. data/lib/fluent/plugin/in_http.rb +11 -1
  28. data/lib/fluent/plugin/in_monitor_agent.rb +1 -1
  29. data/lib/fluent/plugin/in_tail.rb +140 -41
  30. data/lib/fluent/plugin/in_tail/position_file.rb +15 -1
  31. data/lib/fluent/plugin/out_copy.rb +18 -5
  32. data/lib/fluent/plugin/out_exec_filter.rb +3 -3
  33. data/lib/fluent/plugin/out_forward.rb +75 -61
  34. data/lib/fluent/plugin/output.rb +11 -9
  35. data/lib/fluent/plugin/parser_csv.rb +2 -2
  36. data/lib/fluent/plugin/parser_syslog.rb +2 -2
  37. data/lib/fluent/plugin/service_discovery.rb +0 -15
  38. data/lib/fluent/plugin/storage_local.rb +4 -4
  39. data/lib/fluent/plugin_helper/http_server/router.rb +1 -1
  40. data/lib/fluent/plugin_helper/server.rb +4 -2
  41. data/lib/fluent/plugin_helper/service_discovery.rb +39 -1
  42. data/lib/fluent/plugin_helper/service_discovery/manager.rb +11 -5
  43. data/lib/fluent/plugin_helper/socket_option.rb +2 -2
  44. data/lib/fluent/supervisor.rb +16 -1
  45. data/lib/fluent/system_config.rb +14 -0
  46. data/lib/fluent/time.rb +57 -1
  47. data/lib/fluent/version.rb +1 -1
  48. data/templates/new_gem/fluent-plugin.gemspec.erb +3 -3
  49. data/test/command/test_cat.rb +99 -0
  50. data/test/command/test_fluentd.rb +8 -0
  51. data/test/config/test_configurable.rb +1 -1
  52. data/test/config/test_section.rb +9 -0
  53. data/test/config/test_system_config.rb +46 -0
  54. data/test/plugin/in_tail/test_io_handler.rb +4 -4
  55. data/test/plugin/in_tail/test_position_file.rb +58 -4
  56. data/test/plugin/test_file_wrapper.rb +115 -0
  57. data/test/plugin/test_in_exec.rb +1 -1
  58. data/test/plugin/test_in_forward.rb +59 -83
  59. data/test/plugin/test_in_http.rb +58 -40
  60. data/test/plugin/test_in_syslog.rb +66 -56
  61. data/test/plugin/test_in_tail.rb +298 -26
  62. data/test/plugin/test_in_tcp.rb +45 -32
  63. data/test/plugin/test_in_udp.rb +47 -33
  64. data/test/plugin/test_out_copy.rb +87 -0
  65. data/test/plugin/test_out_forward.rb +198 -91
  66. data/test/plugin/test_out_http.rb +1 -1
  67. data/test/plugin/test_out_stream.rb +18 -8
  68. data/test/plugin/test_output.rb +15 -3
  69. data/test/plugin/test_output_as_buffered_backup.rb +2 -0
  70. data/test/plugin/test_parser_csv.rb +14 -0
  71. data/test/plugin/test_parser_syslog.rb +14 -0
  72. data/test/plugin_helper/http_server/test_route.rb +1 -1
  73. data/test/plugin_helper/service_discovery/test_manager.rb +1 -1
  74. data/test/plugin_helper/test_child_process.rb +6 -3
  75. data/test/plugin_helper/test_http_server_helper.rb +34 -27
  76. data/test/plugin_helper/test_server.rb +144 -139
  77. data/test/plugin_helper/test_service_discovery.rb +74 -14
  78. data/test/plugin_helper/test_socket.rb +16 -9
  79. data/test/test_config.rb +2 -1
  80. data/test/test_event.rb +16 -0
  81. data/test/test_formatter.rb +30 -0
  82. data/test/test_output.rb +2 -2
  83. data/test/test_supervisor.rb +35 -0
  84. data/test/test_time_parser.rb +109 -0
  85. metadata +55 -11
  86. data/.travis.yml +0 -77
  87. data/appveyor.yml +0 -31
@@ -22,7 +22,7 @@ module Fluent
22
22
  class Router
23
23
  class NotFoundApp
24
24
  def self.call(req)
25
- [404, { 'Content-Type' => 'text/plain' }, "404 Not Found: #{req.path}\n"]
25
+ [404, { 'Content-Type' => 'text/plain' }, "404 Not Found\n"]
26
26
  end
27
27
  end
28
28
 
@@ -709,11 +709,13 @@ module Fluent
709
709
  return true
710
710
  end
711
711
  rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ETIMEDOUT, Errno::ECONNREFUSED, Errno::EHOSTUNREACH => e
712
- @log.trace "unexpected error before accepting TLS connection", error: e
712
+ @log.trace "unexpected error before accepting TLS connection",
713
+ host: @_handler_socket.peeraddr[3], port: @_handler_socket.peeraddr[1], error: e
713
714
  close rescue nil
714
715
  rescue OpenSSL::SSL::SSLError => e
715
716
  # Use same log level as on_readable
716
- @log.warn "unexpected error before accepting TLS connection by OpenSSL", error: e
717
+ @log.warn "unexpected error before accepting TLS connection by OpenSSL",
718
+ host: @_handler_socket.peeraddr[3], port: @_handler_socket.peeraddr[1], error: e
717
719
  close rescue nil
718
720
  end
719
721
 
@@ -22,10 +22,19 @@ module Fluent
22
22
  module ServiceDiscovery
23
23
  include Fluent::PluginHelper::Timer
24
24
 
25
+ # For the compatibility with older versions without `param_name: :service_discovery_configs`
26
+ attr_reader :service_discovery
27
+
25
28
  def self.included(mod)
26
29
  mod.include ServiceDiscoveryParams
27
30
  end
28
31
 
32
+ def configure(conf)
33
+ super
34
+ # For the compatibility with older versions without `param_name: :service_discovery_configs`
35
+ @service_discovery = @service_discovery_configs
36
+ end
37
+
29
38
  def start
30
39
  unless @discovery_manager
31
40
  log.warn('There is no discovery_manager. skip start them')
@@ -52,6 +61,35 @@ module Fluent
52
61
 
53
62
  private
54
63
 
64
+ # @param title [Symbol] the thread name. this value should be unique.
65
+ # @param static_default_service_directive [String] the directive name of each service when "static" service discovery is enabled in default
66
+ # @param load_balancer [Object] object which has two methods #rebalance and #select_service
67
+ # @param custom_build_method [Proc]
68
+ def service_discovery_configure(title, static_default_service_directive: nil, load_balancer: nil, custom_build_method: nil, interval: 3)
69
+ configs = @service_discovery_configs.map(&:corresponding_config_element)
70
+ if static_default_service_directive
71
+ configs.prepend Fluent::Config::Element.new(
72
+ 'service_discovery',
73
+ '',
74
+ {'@type' => 'static'},
75
+ @config.elements(name: static_default_service_directive.to_s).map{|e| Fluent::Config::Element.new('service', e.arg, e.dup, e.elements, e.unused) }
76
+ )
77
+ end
78
+ service_discovery_create_manager(title, configurations: configs, load_balancer: load_balancer, custom_build_method: custom_build_method, interval: interval)
79
+ end
80
+
81
+ def service_discovery_select_service(&block)
82
+ @discovery_manager.select_service(&block)
83
+ end
84
+
85
+ def service_discovery_services
86
+ @discovery_manager.services
87
+ end
88
+
89
+ def service_discovery_rebalance
90
+ @discovery_manager.rebalance
91
+ end
92
+
55
93
  # @param title [Symbol] the thread name. this value should be unique.
56
94
  # @param configurations [Hash] hash which must has discivery_service type and its configuration like `{ type: :static, conf: <Fluent::Config::Element> }`
57
95
  # @param load_balancer [Object] object which has two methods #rebalance and #select_service
@@ -78,7 +116,7 @@ module Fluent
78
116
  module ServiceDiscoveryParams
79
117
  include Fluent::Configurable
80
118
 
81
- config_section :service_discovery do
119
+ config_section :service_discovery, multi: true, param_name: :service_discovery_configs do
82
120
  config_param :@type, :string
83
121
  end
84
122
  end
@@ -32,17 +32,23 @@ module Fluent
32
32
  @static_config = true
33
33
  end
34
34
 
35
- def configure(opts, parent: nil)
36
- opts.each do |opt|
37
- sd = Fluent::Plugin.new_sd(opt[:type], parent: parent)
38
- sd.configure(opt[:conf])
35
+ def configure(configs, parent: nil)
36
+ configs.each do |config|
37
+ type, conf = if config.has_key?(:conf) # for compatibility with initial API
38
+ [config[:type], config[:conf]]
39
+ else
40
+ [config['@type'], config]
41
+ end
42
+
43
+ sd = Fluent::Plugin.new_sd(type, parent: parent)
44
+ sd.configure(conf)
39
45
 
40
46
  sd.services.each do |s|
41
47
  @services[s.discovery_id] = build_service(s)
42
48
  end
43
49
  @discoveries << sd
44
50
 
45
- if @static_config && opt[:type] != :static
51
+ if @static_config && type.to_sym != :static
46
52
  @static_config = false
47
53
  end
48
54
  end
@@ -38,8 +38,8 @@ module Fluent
38
38
  end
39
39
  end
40
40
  if send_keepalive_packet
41
- if protocol != :tcp
42
- raise ArgumentError, "BUG: send_keepalive_packet is available for tcp"
41
+ if protocol != :tcp && protocol != :tls
42
+ raise ArgumentError, "BUG: send_keepalive_packet is available for tcp/tls"
43
43
  end
44
44
  end
45
45
  end
@@ -385,7 +385,7 @@ module Fluent
385
385
  config_mtime = File.mtime(path)
386
386
 
387
387
  # reuse previous config if last load time is within 5 seconds and mtime of the config file is not changed
388
- if Time.now - Time.at(pre_loadtime) < 5 and config_mtime == pre_config_mtime
388
+ if (Time.now - Time.at(pre_loadtime) < 5) && (config_mtime == pre_config_mtime)
389
389
  return params['pre_conf']
390
390
  end
391
391
 
@@ -518,6 +518,8 @@ module Fluent
518
518
  dl_opts = {}
519
519
  # subtract 1 to match serverengine daemon logger side logging severity.
520
520
  dl_opts[:log_level] = @level - 1
521
+ dl_opts[:log_rotate_age] = @log_rotate_age if @log_rotate_age
522
+ dl_opts[:log_rotate_size] = @log_rotate_size if @log_rotate_size
521
523
  logger = ServerEngine::DaemonLogger.new(@logdev, dl_opts)
522
524
  $log = Fluent::Log.new(logger, @opts)
523
525
  $log.enable_color(false) if @path
@@ -606,6 +608,19 @@ module Fluent
606
608
 
607
609
  @cl_opt = opt
608
610
  @conf = nil
611
+ # parse configuration immediately to initialize logger in early stage
612
+ if @config_path and File.exist?(@config_path)
613
+ @conf = Fluent::Config.build(config_path: @config_path,
614
+ encoding: @conf_encoding ? @conf_encoding : 'utf-8',
615
+ additional_config: @inline_config ? @inline_config : nil,
616
+ use_v1_config: !!@use_v1_config)
617
+ @system_config = build_system_config(@conf)
618
+ if @system_config.log
619
+ @log_rotate_age ||= @system_config.log.rotate_age
620
+ @log_rotate_size ||= @system_config.log.rotate_size
621
+ end
622
+ @conf = nil
623
+ end
609
624
 
610
625
  log_opts = {suppress_repeated_stacktrace: opt[:suppress_repeated_stacktrace], ignore_repeated_log_interval: opt[:ignore_repeated_log_interval],
611
626
  ignore_same_log_interval: opt[:ignore_same_log_interval]}
@@ -55,6 +55,20 @@ module Fluent
55
55
  config_section :log, required: false, init: true, multi: false do
56
56
  config_param :format, :enum, list: [:text, :json], default: :text
57
57
  config_param :time_format, :string, default: '%Y-%m-%d %H:%M:%S %z'
58
+ config_param :rotate_age, default: nil do |v|
59
+ if Fluent::Log::LOG_ROTATE_AGE.include?(v)
60
+ v.to_sym
61
+ else
62
+ begin
63
+ Integer(v)
64
+ rescue ArgumentError => e
65
+ raise Fluent::ConfigError, e.message
66
+ else
67
+ v.to_i
68
+ end
69
+ end
70
+ end
71
+ config_param :rotate_size, :size, default: nil
58
72
  end
59
73
 
60
74
  config_section :counter_server, multi: false do
data/lib/fluent/time.rb CHANGED
@@ -132,13 +132,14 @@ module Fluent
132
132
  end
133
133
 
134
134
  module TimeMixin
135
- TIME_TYPES = ['string', 'unixtime', 'float']
135
+ TIME_TYPES = ['string', 'unixtime', 'float', 'mixed']
136
136
 
137
137
  TIME_PARAMETERS = [
138
138
  [:time_format, :string, {default: nil}],
139
139
  [:localtime, :bool, {default: true}], # UTC if :localtime is false and :timezone is nil
140
140
  [:utc, :bool, {default: false}], # to turn :localtime false
141
141
  [:timezone, :string, {default: nil}],
142
+ [:time_format_fallbacks, :array, {default: []}], # try time_format, then try fallbacks
142
143
  ]
143
144
  TIME_FULL_PARAMETERS = [
144
145
  # To avoid to define :time_type twice (in plugin_helper/inject)
@@ -170,6 +171,12 @@ module Fluent
170
171
  raise Fluent::ConfigError, "both of utc and localtime are specified, use only one of them"
171
172
  end
172
173
 
174
+ if conf.has_key?('time_type') and @time_type == :mixed
175
+ if @time_format.nil? and @time_format_fallbacks.empty?
176
+ raise Fluent::ConfigError, "time_type is :mixed but time_format and time_format_fallbacks is empty."
177
+ end
178
+ end
179
+
173
180
  Fluent::Timezone.validate!(@timezone) if @timezone
174
181
  end
175
182
  end
@@ -180,6 +187,7 @@ module Fluent
180
187
  end
181
188
 
182
189
  def time_parser_create(type: @time_type, format: @time_format, timezone: @timezone, force_localtime: false)
190
+ return MixedTimeParser.new(type, format, @localtime, timezone, @utc, force_localtime, @time_format_fallbacks) if type == :mixed
183
191
  return NumericTimeParser.new(type) if type != :string
184
192
  return TimeParser.new(format, true, nil) if force_localtime
185
193
 
@@ -452,4 +460,52 @@ module Fluent
452
460
  end
453
461
  end
454
462
  end
463
+
464
+ # MixedTimeParser is available when time_type is set to :mixed
465
+ #
466
+ # Use Case 1: primary format is specified explicitly in time_format
467
+ # time_type mixed
468
+ # time_format %iso8601
469
+ # time_format_fallbacks unixtime
470
+ # Use Case 2: time_format is omitted
471
+ # time_type mixed
472
+ # time_format_fallbacks %iso8601, unixtime
473
+ #
474
+ class MixedTimeParser < TimeParser # to include TimeParseError
475
+ def initialize(type, format = nil, localtime = nil, timezone = nil, utc = nil, force_localtime = nil, fallbacks = [])
476
+ @parsers = []
477
+ fallbacks.unshift(format).each do |fallback|
478
+ next unless fallback
479
+ case fallback
480
+ when 'unixtime', 'float'
481
+ @parsers << NumericTimeParser.new(fallback, localtime, timezone)
482
+ else
483
+ if force_localtime
484
+ @parsers << TimeParser.new(fallback, true, nil)
485
+ else
486
+ localtime = localtime && (timezone.nil? && !utc)
487
+ @parsers << TimeParser.new(fallback, localtime, timezone)
488
+ end
489
+ end
490
+ end
491
+ end
492
+
493
+ def parse(value)
494
+ @parsers.each do |parser|
495
+ begin
496
+ Float(value) if parser.class == Fluent::NumericTimeParser
497
+ rescue
498
+ next
499
+ end
500
+ begin
501
+ return parser.parse(value)
502
+ rescue
503
+ # skip TimeParseError
504
+ end
505
+ end
506
+ fallback_class = @parsers.collect do |parser| parser.class end.join(",")
507
+ raise TimeParseError, "invalid time format: value = #{value}, even though fallbacks: #{fallback_class}"
508
+ end
509
+ end
510
+
455
511
  end
@@ -16,6 +16,6 @@
16
16
 
17
17
  module Fluent
18
18
 
19
- VERSION = '1.12.1'
19
+ VERSION = '1.13.1'
20
20
 
21
21
  end
@@ -20,8 +20,8 @@ Gem::Specification.new do |spec|
20
20
  spec.test_files = test_files
21
21
  spec.require_paths = ["lib"]
22
22
 
23
- spec.add_development_dependency "bundler", "~> 1.14"
24
- spec.add_development_dependency "rake", "~> 12.0"
25
- spec.add_development_dependency "test-unit", "~> 3.0"
23
+ spec.add_development_dependency "bundler", "~> <%= bundler_version %>"
24
+ spec.add_development_dependency "rake", "~> <%= rake_version %>"
25
+ spec.add_development_dependency "test-unit", "~> <%= test_unit_version %>"
26
26
  spec.add_runtime_dependency "fluentd", [">= 0.14.10", "< 2"]
27
27
  end
@@ -0,0 +1,99 @@
1
+ require_relative '../helper'
2
+
3
+ require 'test-unit'
4
+ require 'open3'
5
+ require 'fluent/plugin/output'
6
+ require 'fluent/plugin/in_forward'
7
+ require 'fluent/plugin/out_secondary_file'
8
+ require 'fluent/test/driver/output'
9
+ require 'fluent/test/driver/input'
10
+
11
+ class TestFluentCat < ::Test::Unit::TestCase
12
+ def setup
13
+ Fluent::Test.setup
14
+ FileUtils.mkdir_p(TMP_DIR)
15
+ @record = { 'key' => 'value' }
16
+ @time = event_time
17
+ @es = Fluent::OneEventStream.new(@time, @record)
18
+ @primary = create_primary
19
+ metadata = @primary.buffer.new_metadata
20
+ @chunk = create_chunk(@primary, metadata, @es)
21
+ @port = unused_port
22
+ end
23
+
24
+ def teardown
25
+ FileUtils.rm_rf(TMP_DIR)
26
+ @port = nil
27
+ end
28
+
29
+ TMP_DIR = File.expand_path(File.dirname(__FILE__) + "/../tmp/command/fluent_cat#{ENV['TEST_ENV_NUMBER']}")
30
+ FLUENT_CAT_COMMAND = File.expand_path(File.dirname(__FILE__) + "/../../bin/fluent-cat")
31
+
32
+ def config
33
+ %[
34
+ port #{@port}
35
+ bind 127.0.0.1
36
+ ]
37
+ end
38
+
39
+ SECONDARY_CONFIG = %[
40
+ directory #{TMP_DIR}
41
+ ]
42
+
43
+ class DummyOutput < Fluent::Plugin::Output
44
+ def write(chunk); end
45
+ end
46
+
47
+ def create_driver(conf=config)
48
+ Fluent::Test::Driver::Input.new(Fluent::Plugin::ForwardInput).configure(conf)
49
+ end
50
+
51
+ def create_primary(buffer_cofig = config_element('buffer'))
52
+ DummyOutput.new.configure(config_element('ROOT','',{}, [buffer_cofig]))
53
+ end
54
+
55
+ def create_secondary_driver(conf=SECONDARY_CONFIG)
56
+ c = Fluent::Test::Driver::Output.new(Fluent::Plugin::SecondaryFileOutput)
57
+ c.instance.acts_as_secondary(@primary)
58
+ c.configure(conf)
59
+ end
60
+
61
+ def create_chunk(primary, metadata, es)
62
+ primary.buffer.generate_chunk(metadata).tap do |c|
63
+ c.concat(es.to_msgpack_stream, es.size)
64
+ c.commit
65
+ end
66
+ end
67
+
68
+ sub_test_case "json" do
69
+ def test_cat_json
70
+ d = create_driver
71
+ d.run(expect_records: 1) do
72
+ Open3.pipeline_w("ruby #{FLUENT_CAT_COMMAND} --port #{@port} json") do |stdin|
73
+ stdin.puts('{"key":"value"}')
74
+ stdin.close
75
+ end
76
+ end
77
+ event = d.events.first
78
+ assert_equal([1, "json", @record],
79
+ [d.events.size, event.first, event.last])
80
+ end
81
+ end
82
+
83
+ sub_test_case "msgpack" do
84
+ def test_cat_secondary_file
85
+ d = create_secondary_driver
86
+ path = d.instance.write(@chunk)
87
+ d = create_driver
88
+ d.run(expect_records: 1) do
89
+ Open3.pipeline_w("ruby #{FLUENT_CAT_COMMAND} --port #{@port} --format msgpack secondary") do |stdin|
90
+ stdin.write(File.read(path))
91
+ stdin.close
92
+ end
93
+ end
94
+ event = d.events.first
95
+ assert_equal([1, "secondary", @record],
96
+ [d.events.size, event.first, event.last])
97
+ end
98
+ end
99
+ end
@@ -878,6 +878,8 @@ CONF
878
878
  end
879
879
 
880
880
  test "without RUBYOPT" do
881
+ saved_ruby_opt = ENV["RUBYOPT"]
882
+ ENV["RUBYOPT"] = nil
881
883
  conf = <<CONF
882
884
  <source>
883
885
  @type dummy
@@ -889,6 +891,8 @@ CONF
889
891
  CONF
890
892
  conf_path = create_conf_file('rubyopt_test.conf', conf)
891
893
  assert_log_matches(create_cmdline(conf_path), '-Eascii-8bit:ascii-8bit')
894
+ ensure
895
+ ENV["RUBYOPT"] = saved_ruby_opt
892
896
  end
893
897
 
894
898
  test 'invalid values are set to RUBYOPT' do
@@ -912,6 +916,8 @@ CONF
912
916
 
913
917
  # https://github.com/fluent/fluentd/issues/2915
914
918
  test "ruby path contains spaces" do
919
+ saved_ruby_opt = ENV["RUBYOPT"]
920
+ ENV["RUBYOPT"] = nil
915
921
  conf = <<CONF
916
922
  <source>
917
923
  @type dummy
@@ -940,6 +946,8 @@ CONF
940
946
  'spawn command to main:',
941
947
  '-Eascii-8bit:ascii-8bit'
942
948
  )
949
+ ensure
950
+ ENV["RUBYOPT"] = saved_ruby_opt
943
951
  end
944
952
 
945
953
  test 'success to start workers when file buffer is configured in non-workers way only for specific worker' do
@@ -1453,7 +1453,7 @@ module Fluent::Config
1453
1453
  @example = ConfigurableSpec::ExampleWithSkipAccessor.new
1454
1454
  @example.configure(config_element('ROOT'))
1455
1455
  assert_equal 'example7', @example.instance_variable_get(:@name)
1456
- assert_raise NoMethodError.new("undefined method `name' for #{@example}") do
1456
+ assert_raise NoMethodError do
1457
1457
  @example.name
1458
1458
  end
1459
1459
  end