Browse Source

[master] Merge branch 'trac3427' (logging configuration in kea4,kea6,d2)

Conflicts:
	ChangeLog
	configure.ac
	src/bin/dhcp4/kea_controller.cc
	src/bin/dhcp6/kea_controller.cc
	src/bin/dhcp6/main.cc
	src/lib/log/logger_manager.cc
	src/lib/log/logger_unittest_support.cc
	src/lib/log/logger_unittest_support.h
Tomek Mrugalski 10 years ago
parent
commit
64350f6950
40 changed files with 1398 additions and 313 deletions
  1. 5 0
      ChangeLog
  2. 1 0
      Makefile.am
  3. 2 0
      configure.ac
  4. 1 0
      doc/Makefile.am
  5. 19 2
      doc/examples/kea4/several-subnets.json
  6. 21 4
      doc/examples/kea4/single-subnet.json
  7. 19 4
      doc/examples/kea6/several-subnets.json
  8. 59 0
      doc/examples/kea6/simple.json
  9. 86 271
      doc/guide/logging.xml
  10. 11 0
      src/bin/d2/d_controller.cc
  11. 28 0
      src/bin/d2/tests/d2_process_tests.sh.in
  12. 13 2
      src/bin/dhcp4/kea_controller.cc
  13. 22 9
      src/bin/dhcp4/main.cc
  14. 30 0
      src/bin/dhcp4/tests/dhcp4_process_tests.sh.in
  15. 11 2
      src/bin/dhcp6/kea_controller.cc
  16. 22 8
      src/bin/dhcp6/main.cc
  17. 30 0
      src/bin/dhcp6/tests/dhcp6_process_tests.sh.in
  18. 7 0
      src/bin/keactrl/Makefile.am
  19. 19 0
      src/bin/keactrl/kea.conf.in
  20. 33 0
      src/bin/keactrl/tests/keactrl_tests.sh.in
  21. 2 0
      src/lib/dhcpsrv/Makefile.am
  22. 6 1
      src/lib/dhcpsrv/cfgmgr.cc
  23. 15 0
      src/lib/dhcpsrv/cfgmgr.h
  24. 95 0
      src/lib/dhcpsrv/configuration.h
  25. 40 2
      src/lib/dhcpsrv/daemon.cc
  26. 44 2
      src/lib/dhcpsrv/daemon.h
  27. 233 0
      src/lib/dhcpsrv/logging.cc
  28. 116 0
      src/lib/dhcpsrv/logging.h
  29. 1 1
      src/lib/dhcpsrv/subnet.h
  30. 2 0
      src/lib/dhcpsrv/tests/Makefile.am
  31. 10 0
      src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
  32. 63 0
      src/lib/dhcpsrv/tests/configuration_unittest.cc
  33. 51 3
      src/lib/dhcpsrv/tests/daemon_unittest.cc
  34. 224 0
      src/lib/dhcpsrv/tests/logging_unittest.cc
  35. 1 1
      src/lib/log/logger_manager.cc
  36. 5 0
      src/lib/log/logger_name.cc
  37. 6 0
      src/lib/log/logger_name.h
  38. 2 1
      src/lib/log/logger_unittest_support.cc
  39. 5 0
      src/lib/log/tests/logger_name_unittest.cc
  40. 38 0
      tools/path_replacer.sh.in

+ 5 - 0
ChangeLog

@@ -1,3 +1,8 @@
+814.	[func,doc]	tomek
+	It is now possible to specify logging parameters in a
+	configuration file for DHCPv4, DHCPv6 and DHCP-DDNS components.
+	(Trac #3427, git 23285903645c36fc35c6866a74c50c74089cd255)
+
 813.	[func]		tomek
 813.	[func]		tomek
 	Functions, methods and variables referring to BIND10 were renamed
 	Functions, methods and variables referring to BIND10 were renamed
 	to Kea. In particular, system variables (B10_LOGGER_ROOT,
 	to Kea. In particular, system variables (B10_LOGGER_ROOT,

+ 1 - 0
Makefile.am

@@ -129,6 +129,7 @@ cppcheck:
 ### include tool to generate documentation from log message specifications
 ### include tool to generate documentation from log message specifications
 ### in the distributed tarball:
 ### in the distributed tarball:
 EXTRA_DIST = tools/system_messages.py
 EXTRA_DIST = tools/system_messages.py
+EXTRA_DIST += tools/path_replacer.sh
 
 
 #### include external sources in the distributed tarball:
 #### include external sources in the distributed tarball:
 EXTRA_DIST += ext/asio/README
 EXTRA_DIST += ext/asio/README

+ 2 - 0
configure.ac

@@ -1455,6 +1455,7 @@ AC_CONFIG_FILES([compatcheck/Makefile
                  src/lib/util/threads/Makefile
                  src/lib/util/threads/Makefile
                  src/lib/util/threads/tests/Makefile
                  src/lib/util/threads/tests/Makefile
                  src/lib/util/unittests/Makefile
                  src/lib/util/unittests/Makefile
+                 tools/path_replacer.sh
                  tests/Makefile
                  tests/Makefile
                  tests/tools/Makefile
                  tests/tools/Makefile
 ])
 ])
@@ -1472,6 +1473,7 @@ AC_CONFIG_FILES([compatcheck/Makefile
            chmod +x src/lib/log/tests/logger_lock_test.sh
            chmod +x src/lib/log/tests/logger_lock_test.sh
            chmod +x src/lib/log/tests/severity_test.sh
            chmod +x src/lib/log/tests/severity_test.sh
            chmod +x src/lib/util/python/gen_wiredata.py
            chmod +x src/lib/util/python/gen_wiredata.py
+           chmod +x tools/path_replacer.sh
 ])
 ])
 
 
 AC_OUTPUT
 AC_OUTPUT

+ 1 - 0
doc/Makefile.am

@@ -4,6 +4,7 @@ EXTRA_DIST = version.ent.in differences.txt Doxyfile Doxyfile-xml
 
 
 nobase_dist_doc_DATA  = examples/kea4/single-subnet.json
 nobase_dist_doc_DATA  = examples/kea4/single-subnet.json
 nobase_dist_doc_DATA += examples/kea4/several-subnets.json
 nobase_dist_doc_DATA += examples/kea4/several-subnets.json
+nobase_dist_doc_DATA += examples/kea6/simple.json
 nobase_dist_doc_DATA += examples/kea6/several-subnets.json
 nobase_dist_doc_DATA += examples/kea6/several-subnets.json
 
 
 devel:
 devel:

+ 19 - 2
doc/examples/kea4/several-subnets.json

@@ -4,7 +4,7 @@
 
 
 { "Dhcp4":
 { "Dhcp4":
 
 
-{ 
+{
 # Kea is told to listen on eth0 interface only.
 # Kea is told to listen on eth0 interface only.
   "interfaces": [ "eth0" ],
   "interfaces": [ "eth0" ],
 
 
@@ -26,13 +26,30 @@
 
 
 # The following list defines subnets. Each subnet consists of at
 # The following list defines subnets. Each subnet consists of at
 # least subnet and pool entries.
 # least subnet and pool entries.
-  "subnet4": [ 
+  "subnet4": [
   {    "pool": [ "192.0.2.1 - 192.0.2.200" ],
   {    "pool": [ "192.0.2.1 - 192.0.2.200" ],
        "subnet": "192.0.2.0/24"  },
        "subnet": "192.0.2.0/24"  },
   {    "pool": [ "192.0.3.100 - 192.0.3.200" ],
   {    "pool": [ "192.0.3.100 - 192.0.3.200" ],
        "subnet": "192.0.3.0/24"  },
        "subnet": "192.0.3.0/24"  },
   {    "pool": [ "192.0.4.1 - 192.0.4.254" ],
   {    "pool": [ "192.0.4.1 - 192.0.4.254" ],
        "subnet": "192.0.4.0/24"  } ]
        "subnet": "192.0.4.0/24"  } ]
+},
+
+# The following configures logging. Kea will log all debug messages
+# to /var/log/kea-debug.log file.
+"Logging": {
+    "loggers": [
+        {
+            "name": "kea-dhcp4",
+            "output_options": [
+                {
+                    "output": "/var/log/kea-debug.log"
+                }
+            ],
+            "debuglevel": 99,
+            "severity": "DEBUG"
+        }
+    ]
 }
 }
 
 
 }
 }

+ 21 - 4
doc/examples/kea4/single-subnet.json

@@ -4,7 +4,7 @@
 
 
 { "Dhcp4":
 { "Dhcp4":
 
 
-{ 
+{
 # Kea is told to listen on eth0 interface only.
 # Kea is told to listen on eth0 interface only.
   "interfaces": [ "eth0" ],
   "interfaces": [ "eth0" ],
 
 
@@ -30,10 +30,27 @@
 #  "rebind-timer": 2000,
 #  "rebind-timer": 2000,
 
 
 # The following list defines subnets. We have only one subnet
 # The following list defines subnets. We have only one subnet
-# here.
-  "subnet4": [ 
+# here. We tell Kea that it is directly available over local interface.
+  "subnet4": [
   {    "pool": [ "192.0.2.1 - 192.0.2.200" ],
   {    "pool": [ "192.0.2.1 - 192.0.2.200" ],
-       "subnet": "192.0.2.0/24"  } ]
+       "subnet": "192.0.2.0/24",
+       "interface": "eth0"  } ]
+},
+
+# The following configures logging. It assumes that messages with at least
+# informational level (info, warn, error) will will be logged to stdout.
+"Logging": {
+    "loggers": [
+        {
+            "name": "kea-dhcp4",
+            "output_options": [
+                {
+                    "output": "stdout"
+                }
+            ],
+            "severity": "INFO"
+        }
+    ]
 }
 }
 
 
 }
 }

+ 19 - 4
doc/examples/kea6/several-subnets.json

@@ -4,7 +4,7 @@
 
 
 { "Dhcp6":
 { "Dhcp6":
 
 
-{ 
+{
 # Kea is told to listen on eth0 interface only.
 # Kea is told to listen on eth0 interface only.
   "interfaces": [ "eth0" ],
   "interfaces": [ "eth0" ],
 
 
@@ -28,16 +28,31 @@
 
 
 # The following list defines subnets. Each subnet consists of at
 # The following list defines subnets. Each subnet consists of at
 # least subnet and pool entries.
 # least subnet and pool entries.
-  "subnet6": [ 
+  "subnet6": [
   {    "pool": [ "2001:db8:1::/80" ],
   {    "pool": [ "2001:db8:1::/80" ],
        "subnet": "2001:db8:1::/64"  },
        "subnet": "2001:db8:1::/64"  },
   {    "pool": [ "2001:db8:2::/80" ],
   {    "pool": [ "2001:db8:2::/80" ],
-       "subnet": "2001:db8:2::/64"  }, 
+       "subnet": "2001:db8:2::/64"  },
   {    "pool": [ "2001:db8:3::/80" ],
   {    "pool": [ "2001:db8:3::/80" ],
        "subnet": "2001:db8:3::/64"  },
        "subnet": "2001:db8:3::/64"  },
   {    "pool": [ "2001:db8:4::/80" ],
   {    "pool": [ "2001:db8:4::/80" ],
        "subnet": "2001:db8:4::/64"  } ]
        "subnet": "2001:db8:4::/64"  } ]
-}
+},
 
 
+# The following configures logging. It assumes that warning messages
+# will be logged to stdout.
+"Logging": {
+    "loggers": [
+        {
+            "name": "kea-dhcp6",
+            "output_options": [
+                {
+                    "output": "stdout"
+                }
+            ],
+            "severity": "WARN"
+        }
+    ]
 }
 }
 
 
+}

+ 59 - 0
doc/examples/kea6/simple.json

@@ -0,0 +1,59 @@
+# This is an example configuration file for DHCPv6 server in Kea.
+# It's a basic scenario with four IPv6 subnets configured. It is
+# assumed that one subnet (2001:db8:1::/64 is available directly
+# over eth0 interface.
+
+{ "Dhcp6":
+
+{ 
+# Kea is told to listen on eth0 interface only.
+  "interfaces": [ "eth0" ],
+
+# We need to specify lease type. As of May 2014, three backends are supported:
+# memfile, mysql and pgsql. We'll just use memfile, because it doesn't require
+# any prior set up.
+  "lease-database": {
+    "type": "memfile"
+  },
+
+# Addresses will be assigned with preferred and valid lifetimes
+# being 3000 and 4000, respectively. Client is told to start
+# renewing after 1000 seconds. If the server does not repond
+# after 2000 seconds since the lease was granted, client is supposed
+# to start REBIND procedure (emergency renewal that allows switching
+# to a different server).
+  "preferred-lifetime": 3000,
+  "valid-lifetime": 4000,
+  "renew-timer": 1000,
+  "rebind-timer": 2000,
+
+# The following list defines subnets. Each subnet consists of at
+# least subnet and pool entries.
+  "subnet6": [ 
+    {
+      "pool": [ "2001:db8:1::/80" ],
+      "subnet": "2001:db8:1::/64",
+      "interface": "eth0"
+    }
+  ]
+},
+
+# The following configures logging. Kea will log all debug messages
+# to /var/log/kea-debug.log file.
+"Logging": {
+    "loggers": [
+        {
+            "name": "kea-dhcp6",
+            "output_options": [
+                {
+                    "output": "/var/log/kea-debug.log"
+                }
+            ], 
+            "debuglevel": 99,
+            "severity": "DEBUG"
+        }
+    ]
+}
+
+}
+

+ 86 - 271
doc/guide/logging.xml

@@ -55,13 +55,14 @@
           <title>name (string)</title>
           <title>name (string)</title>
 
 
           <para>
           <para>
-          Each logger in the system has a name, the name being that
-          of the component using it to log messages. For instance,
-          if you want to configure logging for the Dhcp4 module,
-          you add an entry for a logger named <quote>Dhcp4</quote>. This
-          configuration will then be used by the loggers in the
-          Dhcp4 module, and all the libraries used by it.
-              </para>
+            Each logger in the system has a name, the name being that
+            of the component using it to log messages. For instance,
+            if you want to configure logging for the Dhcp4 module,
+            you add an entry for a logger named <quote>Dhcp4</quote>. This
+            configuration will then be used by the loggers in the
+            Dhcp4 module, and all the libraries used by it (unless
+            a library defines its own logger).
+          </para>
 
 
 <!-- TODO: later we will have a way to know names of all modules
 <!-- TODO: later we will have a way to know names of all modules
 
 
@@ -78,7 +79,6 @@ Right now you can only see what their names are if they are running
           has the full name of <quote>Dhcp4.dhcpsrv</quote>. If
           has the full name of <quote>Dhcp4.dhcpsrv</quote>. If
           there is no entry in Logging for a particular library,
           there is no entry in Logging for a particular library,
           it will use the configuration given for the module.
           it will use the configuration given for the module.
-
         </para>
         </para>
 
 
        <para>
        <para>
@@ -125,19 +125,40 @@ Right now you can only see what their names are if they are running
         <para>
         <para>
 
 
           One final note about the naming. When specifying the
           One final note about the naming. When specifying the
-          module name within a logger, use the name of the module
-          as specified in <command>bindctl</command>, e.g.
-          <quote>Dhcp4</quote> for the Dhcp4 module,
-          <quote>Dhcp6</quote> for the Dhcp6 module, etc. When
+          module name within a logger, use the name of the binary file,
+          e.g. <quote>kea-dhcp4</quote> for the DHCPv4 module,
+          <quote>kea-dhcp6</quote> for the DHCPv6 module, etc. When
           the message is logged, the message will include the name
           the message is logged, the message will include the name
           of the logger generating the message, but with the module
           of the logger generating the message, but with the module
           name replaced by the name of the process implementing
           name replaced by the name of the process implementing
           the module (so for example, a message generated by the
           the module (so for example, a message generated by the
-          <quote>Dhcp4</quote> logger will appear in the output
+          <quote>DHCPv4</quote> logger will appear in the output
           with a logger name of <quote>kea-dhcp4</quote>).
           with a logger name of <quote>kea-dhcp4</quote>).
 
 
         </para>
         </para>
 
 
+        <para>
+          Currently defined loggers are:
+        </para>
+        <itemizedlist>
+          <listitem>
+            <simpara>kea-dhcp4.dhcp4</simpara>
+          </listitem>
+          <listitem>
+            <simpara>kea-dhcp6.dhcp6</simpara>
+          </listitem>
+          <listitem>
+            <simpara>kea-dhcp-ddns.dhcpddns</simpara>
+          </listitem>
+          <listitem>
+            <simpara>kea-dhcp4.dhcpsrv</simpara>
+          </listitem>
+          <listitem>
+            <simpara>kea-dhcp6.dhcpsrv</simpara>
+          </listitem>
+        </itemizedlist>
+
+        <para>Additional loggers may be defined in the future.</para>
         </section>
         </section>
 
 
         <section>
         <section>
@@ -297,65 +318,22 @@ TODO; there's a ticket to determine these levels, see #1074
           <title>output (string)</title>
           <title>output (string)</title>
 
 
         <para>
         <para>
-
-          Depending on what is set as the output destination, this
-          value is interpreted as follows:
-
+          This value determines the type of output. There are several
+          special values allowed here: <command>stdout</command> (messages
+          are printed on standard output), <command>stderr</command>
+          (messages are printed on stderr), <command>syslog</command> (messages
+          are logged to syslog using default name, <command>syslog:name</command>
+          (messages are logged to syslog using specified name). Any other
+          value is interpreted as a filename that the logs should be written to.
         </para>
         </para>
 
 
-        <variablelist>
-
-          <varlistentry>
-            <term><option>destination</option> is <quote>console</quote></term>
-            <listitem>
-              <para>
-                 The value of output must be one of <quote>stdout</quote>
-                 (messages printed to standard output) or
-                 <quote>stderr</quote> (messages printed to standard
-                 error).
-              </para>
-              <para>
-                Note: if output is set to <quote>stderr</quote> and a lot of
-                messages are produced in a short time (e.g. if the logging
-                level is set to DEBUG), you may occasionally see some messages
-                jumbled up together.  This is due to a combination of the way
-                that messages are written to the screen and the unbuffered
-                nature of the standard error stream.  If this occurs, it is
-                recommended that output be set to <quote>stdout</quote>.
-              </para>
-            </listitem>
-          </varlistentry>
-
-          <varlistentry>
-            <term><option>destination</option> is <quote>file</quote></term>
-            <listitem>
-              <para>
-                The value of output is interpreted as a file name;
-                log messages will be appended to this file.
-              </para>
-            </listitem>
-          </varlistentry>
-
-          <varlistentry>
-            <term><option>destination</option> is <quote>syslog</quote></term>
-            <listitem>
-              <para>
-                The value of output is interpreted as the
-                <command>syslog</command> facility (e.g.
-                <emphasis>local0</emphasis>) that should be used
-                for log messages.
-              </para>
-            </listitem>
-          </varlistentry>
-
-        </variablelist>
-
         <para>
         <para>
 
 
           The other options for <option>output_options</option> are:
           The other options for <option>output_options</option> are:
 
 
         </para>
         </para>
 
 
+        <!-- configuration of flush is not supported yet. 
         <section>
         <section>
           <title>flush (true of false)</title>
           <title>flush (true of false)</title>
 
 
@@ -366,7 +344,7 @@ TODO; there's a ticket to determine these levels, see #1074
             termination are output.
             termination are output.
           </para>
           </para>
 
 
-        </section>
+        </section> -->
 
 
         <section>
         <section>
           <title>maxsize (integer)</title>
           <title>maxsize (integer)</title>
@@ -409,7 +387,7 @@ TODO; there's a ticket to determine these levels, see #1074
           <para>
           <para>
             Maximum number of old log files to keep around when
             Maximum number of old log files to keep around when
             rolling the output file. Only relevant when
             rolling the output file. Only relevant when
-            <option>destination</option> is <quote>file</quote>.
+            <option>output</option> is <quote>file</quote>.
           </para>
           </para>
 
 
         </section>
         </section>
@@ -419,216 +397,53 @@ TODO; there's a ticket to determine these levels, see #1074
       </section>
       </section>
 
 
       <section>
       <section>
-        <title>Example session</title>
+        <title>Example Logger configurations</title>
 
 
         <para>
         <para>
-
           In this example we want to set the global logging to
           In this example we want to set the global logging to
-          write to the file <filename>/var/log/my_bind10.log</filename>,
-          at severity WARN. We want the authoritative server to
-          log at DEBUG with debuglevel 40, to a different file
-          (<filename>/tmp/debug_messages</filename>).
-
-        </para>
-
-        <para>
-
-          Start <command>bindctl</command>.
-
-        </para>
-
-        <para>
-
-           <screen>["login success "]
-&gt; <userinput>config show Logging</userinput>
-Logging/loggers	[]	list
-</screen>
-
-        </para>
-
-        <para>
-
-          By default, no specific loggers are configured, in which
-          case the severity defaults to INFO and the output is
-          written to stderr.
-
-        </para>
-
-        <para>
-
-          Let's first add a default logger:
-
-        </para>
-
-<!-- TODO: adding the empty loggers makes no sense -->
-        <para>
-
-          <screen>&gt; <userinput>config add Logging/loggers</userinput>
-&gt; <userinput>config show Logging</userinput>
-Logging/loggers/	list	(modified)
-</screen>
-
-        </para>
-
-        <para>
-
-          The loggers value line changed to indicate that it is no
-          longer an empty list:
-
-        </para>
-
-        <para>
-
-          <screen>&gt; <userinput>config show Logging/loggers</userinput>
-Logging/loggers[0]/name	""	string	(default)
-Logging/loggers[0]/severity	"INFO"	string	(default)
-Logging/loggers[0]/debuglevel	0	integer	(default)
-Logging/loggers[0]/additive	false	boolean	(default)
-Logging/loggers[0]/output_options	[]	list	(default)
+          write to the console using standard output.
+        </para>
+
+<screen><userinput>
+"Logging": {
+    "loggers": [
+        {
+            "name": "kea-dhcp4",
+            "output_options": [
+                {
+                    "output": "stdout"
+                }
+            ], 
+            "severity": "WARN"
+        }
+    ]
+}
+</userinput>
 </screen>
 </screen>
 
 
-        </para>
-
-        <para>
-
-          The name is mandatory, so we must set it. We will also
-          change the severity as well. Let's start with the global
-          logger.
-
-        </para>
-
-        <para>
-
-          <screen>&gt; <userinput>config set Logging/loggers[0]/name *</userinput>
-&gt; <userinput>config set Logging/loggers[0]/severity WARN</userinput>
-&gt; <userinput>config show Logging/loggers</userinput>
-Logging/loggers[0]/name	"*"	string	(modified)
-Logging/loggers[0]/severity	"WARN"	string	(modified)
-Logging/loggers[0]/debuglevel	0	integer	(default)
-Logging/loggers[0]/additive	false	boolean	(default)
-Logging/loggers[0]/output_options	[]	list	(default)
-</screen>
-
-        </para>
-
-        <para>
-
-          Of course, we need to specify where we want the log
-          messages to go, so we add an entry for an output option.
-
-        </para>
-
-        <para>
-
-          <screen>&gt; <userinput> config add Logging/loggers[0]/output_options</userinput>
-&gt; <userinput> config show Logging/loggers[0]/output_options</userinput>
-Logging/loggers[0]/output_options[0]/destination	"console"	string	(default)
-Logging/loggers[0]/output_options[0]/output	"stdout"	string	(default)
-Logging/loggers[0]/output_options[0]/flush	false	boolean	(default)
-Logging/loggers[0]/output_options[0]/maxsize	0	integer	(default)
-Logging/loggers[0]/output_options[0]/maxver	0	integer	(default)
-</screen>
-
-
-        </para>
-
-        <para>
-
-          These aren't the values we are looking for.
-
-        </para>
-
-        <para>
-
-          <screen>&gt; <userinput> config set Logging/loggers[0]/output_options[0]/destination file</userinput>
-&gt; <userinput> config set Logging/loggers[0]/output_options[0]/output /var/log/kea.log</userinput>
-&gt; <userinput> config set Logging/loggers[0]/output_options[0]/maxsize 204800</userinput>
-&gt; <userinput> config set Logging/loggers[0]/output_options[0]/maxver 8</userinput>
-</screen>
-
-        </para>
-
-        <para>
-
-          Which would make the entire configuration for this logger
-          look like:
-
-        </para>
-
-        <para>
-
-          <screen>&gt; <userinput> config show all Logging/loggers</userinput>
-Logging/loggers[0]/name	"*"	string	(modified)
-Logging/loggers[0]/severity	"WARN"	string	(modified)
-Logging/loggers[0]/debuglevel	0	integer	(default)
-Logging/loggers[0]/additive	false	boolean	(default)
-Logging/loggers[0]/output_options[0]/destination	"file"	string	(modified)
-Logging/loggers[0]/output_options[0]/output	"/var/log/kea.log"	string	(modified)
-Logging/loggers[0]/output_options[0]/flush	false	boolean	(default)
-Logging/loggers[0]/output_options[0]/maxsize	204800	integer	(modified)
-Logging/loggers[0]/output_options[0]/maxver	8	integer	(modified)
-</screen>
-
-        </para>
-
-        <para>
-
-          That looks OK, so let's commit it before we add the
-          configuration for the authoritative server's logger.
-
-        </para>
-
-        <para>
-
-          <screen>&gt; <userinput> config commit</userinput></screen>
-
-        </para>
-
-        <para>
-
-          Now that we have set it, and checked each value along
-          the way, adding a second entry is quite similar.
-
-        </para>
-
-        <para>
-
-          <screen>&gt; <userinput> config add Logging/loggers</userinput>
-&gt; <userinput> config set Logging/loggers[1]/name Dhcp4</userinput>
-&gt; <userinput> config set Logging/loggers[1]/severity DEBUG</userinput>
-&gt; <userinput> config set Logging/loggers[1]/debuglevel 40</userinput>
-&gt; <userinput> config add Logging/loggers[1]/output_options</userinput>
-&gt; <userinput> config set Logging/loggers[1]/output_options[0]/destination file</userinput>
-&gt; <userinput> config set Logging/loggers[1]/output_options[0]/output /tmp/dhcp4_debug.log</userinput>
-&gt; <userinput> config commit</userinput>
-</screen>
-
-        </para>
-
-        <para>
-
-          And that's it. Once we have found whatever it was we
-          needed the debug messages for, we can simply remove the
-          second logger to let the DHCP server use the
-          same settings as the rest.
-
-        </para>
-
-        <para>
-
-          <screen>&gt; <userinput> config remove Logging/loggers[1]</userinput>
-&gt; <userinput> config commit</userinput>
-</screen>
-
-        </para>
-
-        <para>
-
-          And every module will now be using the values from the
-          logger named <quote>*</quote>.
-
-        </para>
-
+<para>In this second example, we want to store debug log messages
+in a file that is at most 2MB and keep up to 8 copies of old logfiles.
+Once the logfile grows to 2MB, it will be renamed and a new file
+file be created.</para>
+
+<screen><userinput>
+"Logging": {
+    "loggers": [
+        {
+            "name": "kea-dhcp6",
+            "output_options": [
+                {
+                    "output": "/var/log/kea-debug.log",
+                    "maxver": 8,
+                    "maxsize": 204800,
+                    "destination": "file"
+                }
+            ], 
+            "severity": "DEBUG",
+            "debuglevel": 99
+        }
+   ]
+}</userinput></screen>
       </section>
       </section>
 
 
     </section>
     </section>

+ 11 - 0
src/bin/d2/d_controller.cc

@@ -18,6 +18,7 @@
 #include <d2/d_controller.h>
 #include <d2/d_controller.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 #include <log/logger_support.h>
 #include <log/logger_support.h>
+#include <dhcpsrv/configuration.h>
 
 
 #include <sstream>
 #include <sstream>
 
 
@@ -220,6 +221,16 @@ DControllerBase::configFromFile() {
         isc::data::ConstElementPtr whole_config =
         isc::data::ConstElementPtr whole_config =
             isc::data::Element::fromJSONFile(config_file, true);
             isc::data::Element::fromJSONFile(config_file, true);
 
 
+        // Let's configure logging before applying the configuration,
+        // so we can log things during configuration process.
+
+        // Temporary storage for logging configuration
+        isc::dhcp::ConfigurationPtr storage(new isc::dhcp::Configuration());
+
+        // Get 'Logging' element from the config and use it to set up
+        // logging. If there's no such element, we'll just pass NULL.
+        Daemon::configureLogger(whole_config->get("Logging"), storage, verbose_);
+
         // Extract derivation-specific portion of the configuration.
         // Extract derivation-specific portion of the configuration.
         module_config = whole_config->get(getAppName());
         module_config = whole_config->get(getAppName());
         if (!module_config) {
         if (!module_config) {

+ 28 - 0
src/bin/d2/tests/d2_process_tests.sh.in

@@ -25,6 +25,20 @@ CONFIG="{
         \"tsig_keys\": [],
         \"tsig_keys\": [],
         \"forward_ddns\" : {},
         \"forward_ddns\" : {},
         \"reverse_ddns\" : {}
         \"reverse_ddns\" : {}
+    },
+    \"Logging\":
+    {
+        \"loggers\": [
+        {
+            \"name\": \"kea-dhcp-ddns\",
+            \"output_options\": [
+                {
+                    \"output\": \"$LOG_FILE\"
+                }
+            ],
+            \"severity\": \"DEBUG\"
+        }
+        ]
     }
     }
 }"
 }"
 
 
@@ -38,6 +52,20 @@ CONFIG_INVALID="{
         \"tsig_keys\": [],
         \"tsig_keys\": [],
         \"forward_ddns\" : {},
         \"forward_ddns\" : {},
         \"reverse_ddns\" : {}
         \"reverse_ddns\" : {}
+    },
+    \"Logging\":
+    {
+        \"loggers\": [
+        {
+            \"name\": \"kea-dhcp-ddns\",
+            \"output_options\": [
+                {
+                    \"output\": \"$LOG_FILE\"
+                }
+            ],
+            \"severity\": \"INFO\"
+        }
+        ]
     }
     }
 }"
 }"
 
 

+ 13 - 2
src/bin/dhcp4/kea_controller.cc

@@ -17,6 +17,7 @@
 #include <dhcp4/json_config_parser.h>
 #include <dhcp4/json_config_parser.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
 #include <dhcp4/dhcp4_log.h>
 #include <dhcp4/dhcp4_log.h>
+#include <dhcpsrv/cfgmgr.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 
 
 #include <string>
 #include <string>
@@ -42,6 +43,7 @@ void configure(const std::string& file_name) {
 
 
     isc::data::ConstElementPtr json;
     isc::data::ConstElementPtr json;
     isc::data::ConstElementPtr dhcp4;
     isc::data::ConstElementPtr dhcp4;
+    isc::data::ConstElementPtr logger;
     isc::data::ConstElementPtr result;
     isc::data::ConstElementPtr result;
 
 
     // Basic sanity check: file name must not be empty.
     // Basic sanity check: file name must not be empty.
@@ -62,6 +64,15 @@ void configure(const std::string& file_name) {
                       " file: " << file_name);
                       " file: " << file_name);
         }
         }
 
 
+        // Let's configure logging before applying the configuration,
+        // so we can log things during configuration process.
+
+        // If there's no logging element, we'll just pass NULL pointer,
+        // which will be handled by configureLogger().
+        Daemon::configureLogger(json->get("Logging"),
+                                CfgMgr::instance().getConfiguration(),
+                                ControlledDhcpv4Srv::getInstance()->getVerbose());
+
         // Get Dhcp4 component from the config
         // Get Dhcp4 component from the config
         dhcp4 = json->get("Dhcp4");
         dhcp4 = json->get("Dhcp4");
 
 
@@ -170,10 +181,10 @@ void ControlledDhcpv4Srv::cleanup() {
 /// This is a logger initialization for JSON file backend.
 /// This is a logger initialization for JSON file backend.
 /// For now, it's just setting log messages to be printed on stdout.
 /// For now, it's just setting log messages to be printed on stdout.
 /// @todo: Implement this properly (see #3427)
 /// @todo: Implement this properly (see #3427)
-void Daemon::loggerInit(const char*, bool verbose) {
+void Daemon::loggerInit(const char* logger_name, bool verbose) {
 
 
     setenv("KEA_LOCKFILE_DIR_FROM_BUILD", "/tmp", 1);
     setenv("KEA_LOCKFILE_DIR_FROM_BUILD", "/tmp", 1);
-    setenv("KEA_LOGGER_ROOT", "kea", 0);
+    setenv("KEA_LOGGER_ROOT", logger_name, 0);
     setenv("KEA_LOGGER_SEVERITY", (verbose ? "DEBUG":"INFO"), 0);
     setenv("KEA_LOGGER_SEVERITY", (verbose ? "DEBUG":"INFO"), 0);
     setenv("KEA_LOGGER_DBGLEVEL", "99", 0);
     setenv("KEA_LOGGER_DBGLEVEL", "99", 0);
     setenv("KEA_LOGGER_DESTINATION",  "stdout", 0);
     setenv("KEA_LOGGER_DESTINATION",  "stdout", 0);

+ 22 - 9
src/bin/dhcp4/main.cc

@@ -39,7 +39,7 @@ namespace {
 
 
 const char* const DHCP4_NAME = "kea-dhcp4";
 const char* const DHCP4_NAME = "kea-dhcp4";
 
 
-const char* const DHCP4_LOGGER_NAME = "kea";
+const char* const DHCP4_LOGGER_NAME = "kea-dhcp4";
 
 
 void
 void
 usage() {
 usage() {
@@ -107,7 +107,6 @@ main(int argc, char* argv[]) {
 
 
     try {
     try {
         // Initialize logging.  If verbose, we'll use maximum verbosity.
         // Initialize logging.  If verbose, we'll use maximum verbosity.
-        // If standalone is enabled, do not buffer initial log messages
         Daemon::loggerInit(DHCP4_LOGGER_NAME, verbose_mode);
         Daemon::loggerInit(DHCP4_LOGGER_NAME, verbose_mode);
         LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_START_INFO)
         LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_START_INFO)
             .arg(getpid()).arg(port_number).arg(verbose_mode ? "yes" : "no");
             .arg(getpid()).arg(port_number).arg(verbose_mode ? "yes" : "no");
@@ -117,19 +116,25 @@ main(int argc, char* argv[]) {
         // Create the server instance.
         // Create the server instance.
         ControlledDhcpv4Srv server(port_number);
         ControlledDhcpv4Srv server(port_number);
 
 
+        // Remember verbose-mode
+        server.setVerbose(verbose_mode);
+
         try {
         try {
             // Initialize the server.
             // Initialize the server.
             server.init(config_file);
             server.init(config_file);
         } catch (const std::exception& ex) {
         } catch (const std::exception& ex) {
-            LOG_ERROR(dhcp4_logger, DHCP4_INIT_FAIL).arg(ex.what());
-
-            // We should not continue if were told to configure (either read
-            // config file or establish Bundy control session).
 
 
-            isc::log::LoggerManager log_manager;
-            log_manager.process();
+            try {
+                // Let's log out what went wrong.
+                isc::log::LoggerManager log_manager;
+                log_manager.process();
+                LOG_ERROR(dhcp4_logger, DHCP4_INIT_FAIL).arg(ex.what());
+            } catch (...) {
+                // The exeption thrown during the initialization could originate
+                // from logger subsystem. Therefore LOG_ERROR() may fail as well.
+                cerr << "Failed to initialize server: " << ex.what() << endl;
+            }
 
 
-            cerr << "Failed to initialize server: " << ex.what() << endl;
             return (EXIT_FAILURE);
             return (EXIT_FAILURE);
         }
         }
 
 
@@ -139,6 +144,14 @@ main(int argc, char* argv[]) {
         LOG_INFO(dhcp4_logger, DHCP4_SHUTDOWN);
         LOG_INFO(dhcp4_logger, DHCP4_SHUTDOWN);
 
 
     } catch (const std::exception& ex) {
     } catch (const std::exception& ex) {
+
+        // First, we print the error on stderr (that should always work)
+        cerr << DHCP4_NAME << ": Fatal error during start up: " << ex.what()
+             << endl;
+
+        // Let's also try to log it using logging system, but we're not
+        // sure if it's usable (the exception may have been thrown from
+        // the logger subsystem)
         LOG_FATAL(dhcp4_logger, DHCP4_SERVER_FAILED).arg(ex.what());
         LOG_FATAL(dhcp4_logger, DHCP4_SERVER_FAILED).arg(ex.what());
         ret = EXIT_FAILURE;
         ret = EXIT_FAILURE;
     }
     }

+ 30 - 0
src/bin/dhcp4/tests/dhcp4_process_tests.sh.in

@@ -34,6 +34,21 @@ CONFIG="{
             \"subnet\": \"10.0.0.0/8\",
             \"subnet\": \"10.0.0.0/8\",
             \"pool\": [ \"10.0.0.10-10.0.0.100\" ]
             \"pool\": [ \"10.0.0.10-10.0.0.100\" ]
         } ]
         } ]
+    },
+
+    \"Logging\":
+    {
+        \"loggers\": [
+        {
+            \"name\": \"kea-dhcp4\",
+            \"output_options\": [
+                {
+                    \"output\": \"$LOG_FILE\"
+                }
+            ],
+            \"severity\": \"INFO\"
+        }
+        ]
     }
     }
 }"
 }"
 # Invalid configuration (negative valid-lifetime) to check that Kea
 # Invalid configuration (negative valid-lifetime) to check that Kea
@@ -55,6 +70,21 @@ CONFIG_INVALID="{
             \"subnet\": \"10.0.0.0/8\",
             \"subnet\": \"10.0.0.0/8\",
             \"pool\": [ \"10.0.0.10-10.0.0.100\" ]
             \"pool\": [ \"10.0.0.10-10.0.0.100\" ]
         } ]
         } ]
+    },
+
+    \"Logging\":
+    {
+        \"loggers\": [
+        {
+            \"name\": \"kea-dhcp4\",
+            \"output_options\": [
+                {
+                    \"output\": \"$LOG_FILE\"
+                }
+            ],
+            \"severity\": \"INFO\"
+        }
+        ]
     }
     }
 }"
 }"
 
 

+ 11 - 2
src/bin/dhcp6/kea_controller.cc

@@ -47,6 +47,7 @@ void configure(const std::string& file_name) {
 
 
     isc::data::ConstElementPtr json;
     isc::data::ConstElementPtr json;
     isc::data::ConstElementPtr dhcp6;
     isc::data::ConstElementPtr dhcp6;
+    isc::data::ConstElementPtr logger;
     isc::data::ConstElementPtr result;
     isc::data::ConstElementPtr result;
 
 
     // Basic sanity check: file name must not be empty.
     // Basic sanity check: file name must not be empty.
@@ -67,6 +68,14 @@ void configure(const std::string& file_name) {
                       + file_name);
                       + file_name);
         }
         }
 
 
+        // Let's configure logging before applying the configuration,
+        // so we can log things during configuration process.
+        // If there's no logging element, we'll just pass NULL pointer,
+        // which will be handled by configureLogger().
+        Daemon::configureLogger(json->get("Logging"),
+                                CfgMgr::instance().getConfiguration(),
+                                ControlledDhcpv6Srv::getInstance()->getVerbose());
+
         // Get Dhcp6 component from the config
         // Get Dhcp6 component from the config
         dhcp6 = json->get("Dhcp6");
         dhcp6 = json->get("Dhcp6");
 
 
@@ -172,10 +181,10 @@ void ControlledDhcpv6Srv::cleanup() {
 /// This is a logger initialization for JSON file backend.
 /// This is a logger initialization for JSON file backend.
 /// For now, it's just setting log messages to be printed on stdout.
 /// For now, it's just setting log messages to be printed on stdout.
 /// @todo: Implement this properly (see #3427)
 /// @todo: Implement this properly (see #3427)
-void Daemon::loggerInit(const char*, bool verbose) {
+void Daemon::loggerInit(const char* logger_name, bool verbose) {
 
 
     setenv("KEA_LOCKFILE_DIR_FROM_BUILD", "/tmp", 1);
     setenv("KEA_LOCKFILE_DIR_FROM_BUILD", "/tmp", 1);
-    setenv("KEA_LOGGER_ROOT", "kea", 0);
+    setenv("KEA_LOGGER_ROOT", logger_name, 0);
     setenv("KEA_LOGGER_SEVERITY", (verbose ? "DEBUG":"INFO"), 0);
     setenv("KEA_LOGGER_SEVERITY", (verbose ? "DEBUG":"INFO"), 0);
     setenv("KEA_LOGGER_DBGLEVEL", "99", 0);
     setenv("KEA_LOGGER_DBGLEVEL", "99", 0);
     setenv("KEA_LOGGER_DESTINATION",  "stdout", 0);
     setenv("KEA_LOGGER_DESTINATION",  "stdout", 0);

+ 22 - 8
src/bin/dhcp6/main.cc

@@ -39,7 +39,7 @@ using namespace std;
 namespace {
 namespace {
 const char* const DHCP6_NAME = "kea-dhcp6";
 const char* const DHCP6_NAME = "kea-dhcp6";
 
 
-const char* const DHCP6_LOGGER_NAME = "kea";
+const char* const DHCP6_LOGGER_NAME = "kea-dhcp6";
 
 
 void
 void
 usage() {
 usage() {
@@ -117,6 +117,9 @@ main(int argc, char* argv[]) {
         // Create the server instance.
         // Create the server instance.
         ControlledDhcpv6Srv server(port_number);
         ControlledDhcpv6Srv server(port_number);
 
 
+        // Remember verbose-mode
+        server.setVerbose(verbose_mode);
+
         try {
         try {
             // Initialize the server, e.g. establish control session
             // Initialize the server, e.g. establish control session
             // if Bundy backend is used or read a configuration file
             // if Bundy backend is used or read a configuration file
@@ -124,14 +127,18 @@ main(int argc, char* argv[]) {
             server.init(config_file);
             server.init(config_file);
 
 
         } catch (const std::exception& ex) {
         } catch (const std::exception& ex) {
-            LOG_ERROR(dhcp6_logger, DHCP6_INIT_FAIL).arg(ex.what());
 
 
-            // We should not continue, just flush whatever has been logged
-            // and exit.
-            isc::log::LoggerManager log_manager;
-            log_manager.process();
+            try {
+                // Let's log out what went wrong.
+                isc::log::LoggerManager log_manager;
+                log_manager.process();
+                LOG_ERROR(dhcp6_logger, DHCP6_INIT_FAIL).arg(ex.what());
+            } catch (...) {
+                // The exeption thrown during the initialization could originate
+                // from logger subsystem. Therefore LOG_ERROR() may fail as well.
+                cerr << "Failed to initialize server: " << ex.what() << endl;
+            }
 
 
-            cerr << "Failed to initialize server: " << ex.what() << endl;
             return (EXIT_FAILURE);
             return (EXIT_FAILURE);
         }
         }
 
 
@@ -141,8 +148,15 @@ main(int argc, char* argv[]) {
         LOG_INFO(dhcp6_logger, DHCP6_SHUTDOWN);
         LOG_INFO(dhcp6_logger, DHCP6_SHUTDOWN);
 
 
     } catch (const std::exception& ex) {
     } catch (const std::exception& ex) {
+
+        // First, we print the error on stderr (that should always work)
+        cerr << DHCP6_NAME << "Fatal error during start up: " << ex.what()
+             << endl;
+
+        // Let's also try to log it using logging system, but we're not
+        // sure if it's usable (the exception may have been thrown from
+        // the logger subsystem)
         LOG_FATAL(dhcp6_logger, DHCP6_SERVER_FAILED).arg(ex.what());
         LOG_FATAL(dhcp6_logger, DHCP6_SERVER_FAILED).arg(ex.what());
-        cerr << "Fatal error during start up: " << ex.what() << endl;
         ret = EXIT_FAILURE;
         ret = EXIT_FAILURE;
     }
     }
 
 

+ 30 - 0
src/bin/dhcp6/tests/dhcp6_process_tests.sh.in

@@ -35,6 +35,21 @@ CONFIG="{
             \"subnet\": \"2001:db8:1::/64\",
             \"subnet\": \"2001:db8:1::/64\",
             \"pool\": [ \"2001:db8:1::10-2001:db8:1::100\" ]
             \"pool\": [ \"2001:db8:1::10-2001:db8:1::100\" ]
         } ]
         } ]
+    },
+
+    \"Logging\":
+    {
+        \"loggers\": [
+        {
+            \"name\": \"kea-dhcp6\",
+            \"output_options\": [
+                {
+                    \"output\": \"$LOG_FILE\"
+                }
+            ],
+            \"severity\": \"INFO\"
+        }
+        ]
     }
     }
 }"
 }"
 # Invalid configuration (negative preferred-lifetime) to check that Kea
 # Invalid configuration (negative preferred-lifetime) to check that Kea
@@ -57,6 +72,21 @@ CONFIG_INVALID="{
             \"subnet\": \"2001:db8:1::/64\",
             \"subnet\": \"2001:db8:1::/64\",
             \"pool\": [ \"2001:db8:1::10-2001:db8:1::100\" ]
             \"pool\": [ \"2001:db8:1::10-2001:db8:1::100\" ]
         } ]
         } ]
+    },
+
+    \"Logging\":
+    {
+        \"loggers\": [
+        {
+            \"name\": \"kea-dhcp6\",
+            \"output_options\": [
+                {
+                    \"output\": \"$LOG_FILE\"
+                }
+            ],
+            \"severity\": \"INFO\"
+        }
+        ]
     }
     }
 }"
 }"
 
 

+ 7 - 0
src/bin/keactrl/Makefile.am

@@ -11,6 +11,10 @@ man_MANS = keactrl.8
 DISTCLEANFILES = keactrl $(CONFIGFILES) $(man_MANS)
 DISTCLEANFILES = keactrl $(CONFIGFILES) $(man_MANS)
 EXTRA_DIST = keactrl.in keactrl.conf.in kea.conf.in $(man_MANS) keactrl.xml
 EXTRA_DIST = keactrl.in keactrl.conf.in kea.conf.in $(man_MANS) keactrl.xml
 
 
+# kea.conf is not really a source used for building other targets, but we need
+# this file to be generated before make install is called.
+BUILT_SOURCES = kea.conf
+
 if GENERATE_DOCS
 if GENERATE_DOCS
 
 
 keactrl.8: keactrl.xml
 keactrl.8: keactrl.xml
@@ -24,6 +28,9 @@ $(man_MANS):
 
 
 endif
 endif
 
 
+kea.conf: kea.conf.in
+	$(top_srcdir)/tools/path_replacer.sh $< $@
+
 if INSTALL_CONFIGURATIONS
 if INSTALL_CONFIGURATIONS
 
 
 install-data-local:
 install-data-local:

+ 19 - 0
src/bin/keactrl/kea.conf.in

@@ -65,6 +65,25 @@
   "tsig_keys": [],
   "tsig_keys": [],
   "forward_ddns" : {},
   "forward_ddns" : {},
   "reverse_ddns" : {}
   "reverse_ddns" : {}
+},
+
+# Logging configuration starts here. It tells Kea servers to store
+# all log messages (on severity INFO or more) in a file.
+# debuglevel variable is used on DEBUG level only.
+"Logging":
+{
+  "loggers": [
+    {
+      "name": "kea",
+      "output_options": [
+          {
+            "output": "@localstatedir@/log/kea.log"
+          }
+      ],
+      "severity": "INFO",
+      "debuglevel": 0
+    }
+  ]
 }
 }
 
 
 }
 }

+ 33 - 0
src/bin/keactrl/tests/keactrl_tests.sh.in

@@ -75,6 +75,39 @@ config="{
         \"tsig_keys\": [],
         \"tsig_keys\": [],
         \"forward_ddns\" : {},
         \"forward_ddns\" : {},
         \"reverse_ddns\" : {}
         \"reverse_ddns\" : {}
+    },
+
+    \"Logging\":
+    {
+        \"loggers\": [
+        {
+            \"name\": \"kea-dhcp4\",
+            \"output_options\": [
+                {
+                    \"output\": \"$LOG_FILE\"
+                }
+            ],
+            \"severity\": \"INFO\"
+        },
+        {
+            \"name\": \"kea-dhcp6\",
+            \"output_options\": [
+                {
+                    \"output\": \"$LOG_FILE\"
+                }
+            ],
+            \"severity\": \"INFO\"
+        },
+        {
+            \"name\": \"kea-dhcp-ddns\",
+            \"output_options\": [
+                {
+                    \"output\": \"$LOG_FILE\"
+                }
+            ],
+            \"severity\": \"INFO\"
+        }
+        ]
     }
     }
 }"
 }"
 
 

+ 2 - 0
src/lib/dhcpsrv/Makefile.am

@@ -59,6 +59,8 @@ libkea_dhcpsrv_la_SOURCES += key_from_key.h
 libkea_dhcpsrv_la_SOURCES += lease.cc lease.h
 libkea_dhcpsrv_la_SOURCES += lease.cc lease.h
 libkea_dhcpsrv_la_SOURCES += lease_mgr.cc lease_mgr.h
 libkea_dhcpsrv_la_SOURCES += lease_mgr.cc lease_mgr.h
 libkea_dhcpsrv_la_SOURCES += lease_mgr_factory.cc lease_mgr_factory.h
 libkea_dhcpsrv_la_SOURCES += lease_mgr_factory.cc lease_mgr_factory.h
+libkea_dhcpsrv_la_SOURCES += logging.cc logging.h
+libkea_dhcpsrv_la_SOURCES += configuration.h
 libkea_dhcpsrv_la_SOURCES += memfile_lease_mgr.cc memfile_lease_mgr.h
 libkea_dhcpsrv_la_SOURCES += memfile_lease_mgr.cc memfile_lease_mgr.h
 
 
 if HAVE_MYSQL
 if HAVE_MYSQL

+ 6 - 1
src/lib/dhcpsrv/cfgmgr.cc

@@ -437,10 +437,15 @@ CfgMgr::getD2ClientMgr() {
     return (d2_client_mgr_);
     return (d2_client_mgr_);
 }
 }
 
 
+ConfigurationPtr
+CfgMgr::getConfiguration() {
+    return (configuration_);
+}
+
 CfgMgr::CfgMgr()
 CfgMgr::CfgMgr()
     : datadir_(DHCP_DATA_DIR),
     : datadir_(DHCP_DATA_DIR),
       all_ifaces_active_(false), echo_v4_client_id_(true),
       all_ifaces_active_(false), echo_v4_client_id_(true),
-      d2_client_mgr_() {
+      d2_client_mgr_(), configuration_(new Configuration()) {
     // DHCP_DATA_DIR must be set set with -DDHCP_DATA_DIR="..." in Makefile.am
     // DHCP_DATA_DIR must be set set with -DDHCP_DATA_DIR="..." in Makefile.am
     // Note: the definition of DHCP_DATA_DIR needs to include quotation marks
     // Note: the definition of DHCP_DATA_DIR needs to include quotation marks
     // See AM_CPPFLAGS definition in Makefile.am
     // See AM_CPPFLAGS definition in Makefile.am

+ 15 - 0
src/lib/dhcpsrv/cfgmgr.h

@@ -24,6 +24,7 @@
 #include <dhcpsrv/option_space_container.h>
 #include <dhcpsrv/option_space_container.h>
 #include <dhcpsrv/pool.h>
 #include <dhcpsrv/pool.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet.h>
+#include <dhcpsrv/configuration.h>
 #include <util/buffer.h>
 #include <util/buffer.h>
 
 
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
@@ -420,6 +421,12 @@ public:
     /// @return a reference to the DHCP-DDNS manager.
     /// @return a reference to the DHCP-DDNS manager.
     D2ClientMgr& getD2ClientMgr();
     D2ClientMgr& getD2ClientMgr();
 
 
+
+    /// @brief Returns the current configuration.
+    ///
+    /// @return a pointer to the current configuration.
+    ConfigurationPtr getConfiguration();
+
 protected:
 protected:
 
 
     /// @brief Protected constructor.
     /// @brief Protected constructor.
@@ -516,6 +523,14 @@ private:
 
 
     /// @brief Manages the DHCP-DDNS client and its configuration.
     /// @brief Manages the DHCP-DDNS client and its configuration.
     D2ClientMgr d2_client_mgr_;
     D2ClientMgr d2_client_mgr_;
+
+    /// @brief Configuration
+    ///
+    /// This is a structure that will hold all configuration.
+    /// @todo: migrate all other parameters to that structure.
+    /// @todo: maybe this should be a vector<Configuration>, so we could keep
+    ///        previous configurations and do a rollback if needed?
+    ConfigurationPtr configuration_;
 };
 };
 
 
 } // namespace isc::dhcp
 } // namespace isc::dhcp

+ 95 - 0
src/lib/dhcpsrv/configuration.h

@@ -0,0 +1,95 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DHCPSRV_CONFIGURATION_H
+#define DHCPSRV_CONFIGURATION_H
+
+#include <log/logger_level.h>
+#include <boost/shared_ptr.hpp>
+#include <vector>
+#include <stdint.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Defines single logging destination
+///
+/// This structure is used to keep log4cplus configuration parameters.
+struct LoggingDestination {
+
+    /// @brief defines logging destination output
+    ///
+    /// Values accepted are: stdout, stderr, syslog, syslog:name.
+    /// Any other destination will be considered a file name.
+    std::string output_;
+    
+    /// @brief Maximum number of log files in rotation
+    int maxver_;
+
+    /// @brief Maximum log file size
+    uint64_t maxsize_;
+};
+
+/// @brief structure that describes one logging entry
+///
+/// This is a structure that conveys one logger entry configuration.
+/// The structure in JSON form has the following syntax:
+///        {
+///            "name": "*",
+///            "output_options": [
+///                {
+///                    "output": "/path/to/the/logfile.log",
+///                    "maxver": 8,
+///                    "maxsize": 204800
+///                }
+///            ], 
+///            "severity": "WARN",
+///            "debuglevel": 99
+///        }, 
+struct LoggingInfo {
+
+    /// @brief logging name
+    std::string name_;
+
+    /// @brief describes logging severity
+    isc::log::Severity severity_;
+
+    /// @brief debuglevel (used when severity_ == DEBUG)
+    ///
+    /// We use range 0(least verbose)..99(most verbose)
+    int debuglevel_;
+
+    /// @brief specific logging destinations
+    std::vector<LoggingDestination> destinations_;
+};
+
+/// @brief storage for logging information in log4cplus format
+typedef std::vector<isc::dhcp::LoggingInfo> LoggingInfoStorage;
+
+/// @brief Specifies current DHCP configuration
+///
+/// @todo Migrate all other configuration parameters from cfgmgr.h here
+struct Configuration {
+
+    /// @brief logging specific information
+    LoggingInfoStorage logging_info_;
+};
+
+/// @brief pointer to the configuration
+typedef boost::shared_ptr<Configuration> ConfigurationPtr;
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // DHCPSRV_CONFIGURATION_H

+ 40 - 2
src/lib/dhcpsrv/daemon.cc

@@ -15,11 +15,13 @@
 #include <config.h>
 #include <config.h>
 #include <dhcpsrv/daemon.h>
 #include <dhcpsrv/daemon.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
+#include <cc/data.h>
 #include <boost/bind.hpp>
 #include <boost/bind.hpp>
+#include <logging.h>
 #include <errno.h>
 #include <errno.h>
 
 
 /// @brief provides default implementation for basic daemon operations
 /// @brief provides default implementation for basic daemon operations
-/// 
+///
 /// This file provides stub implementations that are expected to be redefined
 /// This file provides stub implementations that are expected to be redefined
 /// in derived classes (e.g. ControlledDhcpv6Srv)
 /// in derived classes (e.g. ControlledDhcpv6Srv)
 namespace isc {
 namespace isc {
@@ -29,7 +31,7 @@ namespace dhcp {
 std::string Daemon::config_file_ = "";
 std::string Daemon::config_file_ = "";
 
 
 Daemon::Daemon()
 Daemon::Daemon()
-    : signal_set_(), signal_handler_() {
+    : signal_set_(), signal_handler_(), verbose_(false) {
 }
 }
 
 
 Daemon::~Daemon() {
 Daemon::~Daemon() {
@@ -53,6 +55,42 @@ void Daemon::handleSignal() {
     }
     }
 }
 }
 
 
+void Daemon::configureLogger(const isc::data::ConstElementPtr& log_config,
+                             const ConfigurationPtr& storage,
+                             bool verbose) {
+
+    // This is utility class that translates JSON structures into formats
+    // understandable by log4cplus.
+    LogConfigParser parser(storage);
+
+    if (!log_config) {
+        // There was no logger configuration. Let's clear any config
+        // and revert to the default.
+
+        parser.applyDefaultConfiguration(verbose); // Set up default logging
+        return;
+    }
+
+    isc::data::ConstElementPtr loggers;
+    loggers = log_config->get("loggers");
+    if (!loggers) {
+        // There is Logging structure, but it doesn't have loggers
+        // array in it. Let's clear any old logging configuration
+        // we may have and revert to the default.
+
+        parser.applyDefaultConfiguration(verbose); // Set up default logging
+        return;
+    }
+
+    // Translate JSON structures into log4cplus formats
+    parser.parseConfiguration(loggers, verbose);
+
+    // Apply the configuration
+
+    /// @todo: Once configuration unrolling is implemented,
+    /// this call will be moved to a separate method.
+    parser.applyConfiguration();
+}
 
 
 };
 };
 };
 };

+ 44 - 2
src/lib/dhcpsrv/daemon.h

@@ -12,12 +12,16 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 
 
+#ifndef DAEMON_H
+#define DAEMON_H
+
 #include <config.h>
 #include <config.h>
+#include <cc/data.h>
+#include <dhcpsrv/configuration.h>
 #include <util/signal_set.h>
 #include <util/signal_set.h>
 #include <boost/noncopyable.hpp>
 #include <boost/noncopyable.hpp>
 #include <string>
 #include <string>
 
 
-
 namespace isc {
 namespace isc {
 namespace dhcp {
 namespace dhcp {
 
 
@@ -105,7 +109,7 @@ public:
         return (config_file_);
         return (config_file_);
     }
     }
 
 
-    /// Initializes logger
+    /// @brief Initializes logger
     ///
     ///
     /// This method initializes logger. Currently its implementation is specific
     /// This method initializes logger. Currently its implementation is specific
     /// to each configuration backend.
     /// to each configuration backend.
@@ -114,6 +118,39 @@ public:
     /// @param verbose verbose mode (true usually enables DEBUG messages)
     /// @param verbose verbose mode (true usually enables DEBUG messages)
     static void loggerInit(const char* log_name, bool verbose);
     static void loggerInit(const char* log_name, bool verbose);
 
 
+    /// @brief Configures logger
+    ///
+    /// Applies configuration stored in "Logging" structure in the
+    /// configuration file. This structure has a "loggers" array that
+    /// contains 0 or more entries, each configuring one logging source
+    /// (name, severity, debuglevel), each with zero or more outputs (file,
+    /// maxsize, maximum number of files).
+    ///
+    /// @param log_config JSON structures that describe logging
+    /// @param storage configuration will be stored here
+    /// @param verbose specifies if verbose mode should be enabled
+    static void configureLogger(const isc::data::ConstElementPtr& log_config,
+                                const isc::dhcp::ConfigurationPtr& storage,
+                                bool verbose);
+
+    /// @brief Sets or clears verbose mode
+    ///
+    /// Verbose mode (-v in command-line) triggers loggers to log everythin
+    /// (sets severity to DEBUG and debuglevel to 99). Values specified in the
+    /// config file are ignored.
+    ///
+    /// @param verbose specifies if verbose should be set or not
+    void setVerbose(bool verbose) {
+        verbose_ = verbose;
+    }
+
+    /// @brief Returns if running in verbose mode
+    ///
+    /// @return verbose mode
+    bool getVerbose() const {
+        return (verbose_);
+    }
+
 protected:
 protected:
 
 
     /// @brief Invokes handler for the next received signal.
     /// @brief Invokes handler for the next received signal.
@@ -146,7 +183,12 @@ private:
 
 
     /// @brief Config file name or empty if config file not used.
     /// @brief Config file name or empty if config file not used.
     static std::string config_file_;
     static std::string config_file_;
+
+    /// @brief Verbose mode
+    bool verbose_;
 };
 };
 
 
 }; // end of isc::dhcp namespace
 }; // end of isc::dhcp namespace
 }; // end of isc namespace
 }; // end of isc namespace
+
+#endif

+ 233 - 0
src/lib/dhcpsrv/logging.cc

@@ -0,0 +1,233 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <cc/data.h>
+#include <dhcpsrv/logging.h>
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+#include <log/logger_specification.h>
+#include <log/logger_manager.h>
+#include <log/logger_name.h>
+
+using namespace isc::data;
+using namespace isc::log;
+
+namespace isc {
+namespace dhcp {
+
+LogConfigParser::LogConfigParser(const ConfigurationPtr& storage)
+    :config_(storage), verbose_(false) {
+    if (!storage) {
+        isc_throw(BadValue, "LogConfigParser needs a pointer to the "
+                  "configuration, so parsed data can be stored there");
+    }
+}
+
+void LogConfigParser::parseConfiguration(const isc::data::ConstElementPtr& loggers,
+                                         bool verbose) {
+    verbose_ = verbose;
+
+    // Iterate over all entries in "Logging/loggers" list
+    BOOST_FOREACH(ConstElementPtr logger, loggers->listValue()) {
+        parseConfigEntry(logger);
+    }
+}
+
+void LogConfigParser::parseConfigEntry(isc::data::ConstElementPtr entry) {
+    if (!entry) {
+        // This should not happen, but let's be on the safe side and check
+        return;
+    }
+
+    if (!config_) {
+        isc_throw(BadValue, "configuration storage not set, can't parse logger config.");
+    }
+
+    LoggingInfo info;
+
+    // Get a name
+    isc::data::ConstElementPtr name_ptr = entry->get("name");
+    if (!name_ptr) {
+        isc_throw(BadValue, "loggers entry does not have a mandatory 'name' "
+                  "element (" << entry->getPosition() << ")");
+    }
+    info.name_ = name_ptr->stringValue();
+
+    // Get severity
+    isc::data::ConstElementPtr severity_ptr = entry->get("severity");
+    if (!name_ptr) {
+        isc_throw(BadValue, "loggers entry does not have a mandatory "
+                  "'severity' element (" << entry->getPosition() << ")");
+    }
+    try {
+        info.severity_ = isc::log::getSeverity(severity_ptr->stringValue().c_str());
+    } catch (const std::exception& ex) {
+        isc_throw(BadValue, "Unsupported severity value '"
+                  << severity_ptr->stringValue() << "' ("
+                  << severity_ptr->getPosition() << ")");
+    }
+
+    // Get debug logging level
+    info.debuglevel_ = 0;
+    isc::data::ConstElementPtr debuglevel_ptr = entry->get("debuglevel");
+
+    // It's ok to not have debuglevel, we'll just assume its least verbose
+    // (0) level.
+    if (debuglevel_ptr) {
+        try {
+            info.debuglevel_ = boost::lexical_cast<int>(debuglevel_ptr->str());
+            if ( (info.debuglevel_ < 0) || (info.debuglevel_ > 99) ) {
+                // Comment doesn't matter, it is caught several lines below
+                isc_throw(BadValue, "");
+            }
+        } catch (...) {
+            isc_throw(BadValue, "Unsupported debuglevel value '"
+                      << debuglevel_ptr->stringValue()
+                      << "', expected 0-99 ("
+                      << debuglevel_ptr->getPosition() << ")");
+        }
+    }
+
+    // We want to follow the normal path, so it could catch parsing errors even
+    // when verbose mode is enabled. If it is, just override whatever was parsed
+    // in the config file.
+    if (verbose_) {
+        info.severity_ = isc::log::DEBUG;
+        info.debuglevel_ = 99;
+    }
+
+    isc::data::ConstElementPtr output_options = entry->get("output_options");
+
+    if (output_options) {
+        parseOutputOptions(info.destinations_, output_options);
+    }
+    
+    config_->logging_info_.push_back(info);
+}
+
+void LogConfigParser::parseOutputOptions(std::vector<LoggingDestination>& destination,
+                                         isc::data::ConstElementPtr output_options) {
+    if (!output_options) {
+        isc_throw(BadValue, "Missing 'output_options' structure in 'loggers'");
+    }
+    BOOST_FOREACH(ConstElementPtr output_option, output_options->listValue()) {
+
+        LoggingDestination dest;
+
+        isc::data::ConstElementPtr output = output_option->get("output");
+        if (!output) {
+            isc_throw(BadValue, "output_options entry does not have a mandatory 'output' "
+                      "element (" << output_option->getPosition() << ")");
+        }
+        dest.output_ = output->stringValue();
+
+        isc::data::ConstElementPtr maxver_ptr = output_option->get("maxver");
+        if (maxver_ptr) {
+            dest.maxver_ = boost::lexical_cast<int>(maxver_ptr->str());
+        }
+
+        isc::data::ConstElementPtr maxsize_ptr = output_option->get("maxsize");
+        if (maxsize_ptr) {
+            dest.maxsize_ = boost::lexical_cast<uint64_t>(maxsize_ptr->str());
+        }
+
+        destination.push_back(dest);
+    }
+}
+
+void LogConfigParser::applyConfiguration() {
+
+    static const std::string STDOUT = "stdout";
+    static const std::string STDERR = "stderr";
+    static const std::string SYSLOG = "syslog";
+    static const std::string SYSLOG_COLON = "syslog:";
+
+    // Set locking directory to /tmp
+    setenv("B10_LOCKFILE_DIR_FROM_BUILD", "/tmp", 1);
+
+    std::vector<LoggerSpecification> specs;
+
+    // Now iterate through all specified loggers
+    for (LoggingInfoStorage::const_iterator it = config_->logging_info_.begin();
+         it != config_->logging_info_.end(); ++it) {
+
+        // Prepare the objects to define the logging specification
+        LoggerSpecification spec(it->name_,
+                                 it->severity_,
+                                 it->debuglevel_);
+        OutputOption option;
+
+        for (std::vector<LoggingDestination>::const_iterator dest = it->destinations_.begin();
+             dest != it->destinations_.end(); ++dest) {
+            
+            // Set up output option according to destination specification
+            if (dest->output_ == STDOUT) {
+                option.destination = OutputOption::DEST_CONSOLE;
+                option.stream = OutputOption::STR_STDOUT;
+
+            } else if (dest->output_ == STDERR) {
+                option.destination = OutputOption::DEST_CONSOLE;
+                option.stream = OutputOption::STR_STDERR;
+
+            } else if (dest->output_ == SYSLOG) {
+                option.destination = OutputOption::DEST_SYSLOG;
+                // Use default specified in OutputOption constructor for the
+                // syslog destination
+
+            } else if (dest->output_.find(SYSLOG_COLON) == 0) {
+                option.destination = OutputOption::DEST_SYSLOG;
+                // Must take account of the string actually being "syslog:"
+                if (dest->output_ == SYSLOG_COLON) {
+                    // The expected syntax is syslog:facility. User skipped
+                    // the logging name, so we'll just use the default ("kea")
+                    option.facility = isc::log::getDefaultRootLoggerName();
+                    
+                } else {
+                    // Everything else in the string is the facility name
+                    option.facility = dest->output_.substr(SYSLOG_COLON.size());
+                }
+
+            } else {
+                // Not a recognised destination, assume a file.
+                option.destination = OutputOption::DEST_FILE;
+                option.filename = dest->output_;
+            }
+
+            // ... and set the destination
+            spec.addOutputOption(option);
+        }
+
+        specs.push_back(spec);
+    }
+
+    LoggerManager manager;
+    manager.process(specs.begin(), specs.end());
+}
+
+void LogConfigParser::applyDefaultConfiguration(bool verbose) {
+    LoggerSpecification spec("kea", (verbose?isc::log::DEBUG : isc::log::INFO),
+                             (verbose?99:0));
+
+    OutputOption option;
+    option.destination = OutputOption::DEST_CONSOLE;
+    option.stream = OutputOption::STR_STDOUT;
+
+    spec.addOutputOption(option);
+
+    LoggerManager manager;
+    manager.process(spec);
+}
+
+} // namespace isc::dhcp
+} // namespace isc

+ 116 - 0
src/lib/dhcpsrv/logging.h

@@ -0,0 +1,116 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DHCPSRV_LOGGING_H
+#define DHCPSRV_LOGGING_H
+
+#include <cc/data.h>
+#include <dhcpsrv/configuration.h>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Configures log4cplus by translating Kea configuration structures
+///
+/// This parser iterates over provided data elements and translates them
+/// into values applicable to log4cplus.
+///
+/// The data structures converted to JSON format have the following syntax:
+/// {
+///     "name": "kea",
+///     "output_options": [
+///         {
+///             "output": "/home/thomson/kea-inst/kea-warn.log",
+///             "maxver": 8,
+///             "maxsize": 204800
+///         }
+///     ],
+///     "severity": "WARN"
+/// }
+///
+/// This is only an example and actual values may be different.
+///
+/// The data structures don't have to originate from JSON. JSON is just a
+/// convenient presentation syntax.
+///
+/// This class uses Configuration structure to store logging configuration.
+class LogConfigParser {
+public:
+
+    /// @brief Constructor
+    ///
+    /// @param storage parsed logging configuration will be stored here
+    LogConfigParser(const ConfigurationPtr& storage);
+
+    /// @brief Parses specified configuration
+    ///
+    /// Walks over specified logging configuration JSON structures and store
+    /// parsed information in config_->logging_info_.
+    ///
+    /// @param log_config JSON structures to be parsed (loggers list)
+    /// @param verbose specifies verbose mode (true forces DEBUG, debuglevel = 99)
+    void parseConfiguration(const isc::data::ConstElementPtr& log_config,
+                            bool verbose = false);
+
+    /// @brief Applies stored configuration
+    void applyConfiguration();
+
+    /// @brief Configures default logging
+    ///
+    /// This method is static,
+    ///
+    /// @param verbose specifies verbose mode (true forces DEBUG, debuglevel = 99)
+    void applyDefaultConfiguration(bool verbose = false);
+
+private:
+
+    /// @brief Parses one JSON structure in Logging/loggers" array
+    ///
+    /// @param entry JSON structure to be parsed
+    /// @brief parses one structure in Logging/loggers.
+    void parseConfigEntry(isc::data::ConstElementPtr entry);
+
+    /// @brief Parses output_options structure
+    ///
+    /// An example data structure that holds output_options in JSON format
+    /// looks like this:
+    ///     "output_options": [
+    ///         {
+    ///             "output": "/var/log/kea-warn.log",
+    ///             "maxver": 8,
+    ///             "maxsize": 204800
+    ///         }
+    ///     ],
+    /// @param destination parsed parameters will be stored here
+    /// @param output_options element to be parsed
+    void parseOutputOptions(std::vector<LoggingDestination>& destination,
+                            isc::data::ConstElementPtr output_options);
+
+    /// @brief Configuration is stored here
+    ///
+    /// LogConfigParser class uses only config_->logging_info_ field.
+    ConfigurationPtr config_;
+
+    /// @brief Verbose mode
+    ///
+    /// When verbose mode is enabled, logging severity is overridden to DEBUG,
+    /// and debuglevel is always 99.
+    bool verbose_;
+};
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // DHCPSRV_LOGGING_H

+ 1 - 1
src/lib/dhcpsrv/subnet.h

@@ -778,4 +778,4 @@ typedef std::vector<Subnet6Ptr> Subnet6Collection;
 } // end of isc::dhcp namespace
 } // end of isc::dhcp namespace
 } // end of isc namespace
 } // end of isc namespace
 
 
-#endif // SUBNET_T
+#endif // SUBNET_H

+ 2 - 0
src/lib/dhcpsrv/tests/Makefile.am

@@ -55,6 +55,7 @@ libdhcpsrv_unittests_SOURCES  = run_unittests.cc
 libdhcpsrv_unittests_SOURCES += addr_utilities_unittest.cc
 libdhcpsrv_unittests_SOURCES += addr_utilities_unittest.cc
 libdhcpsrv_unittests_SOURCES += alloc_engine_unittest.cc
 libdhcpsrv_unittests_SOURCES += alloc_engine_unittest.cc
 libdhcpsrv_unittests_SOURCES += callout_handle_store_unittest.cc
 libdhcpsrv_unittests_SOURCES += callout_handle_store_unittest.cc
+libdhcpsrv_unittests_SOURCES += configuration_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfgmgr_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfgmgr_unittest.cc
 libdhcpsrv_unittests_SOURCES += csv_lease_file4_unittest.cc
 libdhcpsrv_unittests_SOURCES += csv_lease_file4_unittest.cc
 libdhcpsrv_unittests_SOURCES += csv_lease_file6_unittest.cc
 libdhcpsrv_unittests_SOURCES += csv_lease_file6_unittest.cc
@@ -66,6 +67,7 @@ libdhcpsrv_unittests_SOURCES += lease_file_io.cc lease_file_io.h
 libdhcpsrv_unittests_SOURCES += lease_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_mgr_factory_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_mgr_factory_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_mgr_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_mgr_unittest.cc
+libdhcpsrv_unittests_SOURCES += logging_unittest.cc
 libdhcpsrv_unittests_SOURCES += generic_lease_mgr_unittest.cc generic_lease_mgr_unittest.h
 libdhcpsrv_unittests_SOURCES += generic_lease_mgr_unittest.cc generic_lease_mgr_unittest.h
 libdhcpsrv_unittests_SOURCES += memfile_lease_mgr_unittest.cc
 libdhcpsrv_unittests_SOURCES += memfile_lease_mgr_unittest.cc
 libdhcpsrv_unittests_SOURCES += dhcp_parsers_unittest.cc
 libdhcpsrv_unittests_SOURCES += dhcp_parsers_unittest.cc

+ 10 - 0
src/lib/dhcpsrv/tests/cfgmgr_unittest.cc

@@ -289,6 +289,16 @@ public:
     isc::dhcp::ClientClasses classify_;
     isc::dhcp::ClientClasses classify_;
 };
 };
 
 
+// Checks that there is a configuration structure available and that
+// it is empty by default.
+TEST_F(CfgMgrTest, configuration) {
+
+    ConfigurationPtr configuration = CfgMgr::instance().getConfiguration();
+    ASSERT_TRUE(configuration);
+
+    EXPECT_TRUE(configuration->logging_info_.empty());
+}
+
 // This test verifies that multiple option definitions can be added
 // This test verifies that multiple option definitions can be added
 // under different option spaces.
 // under different option spaces.
 TEST_F(CfgMgrTest, getOptionDefs) {
 TEST_F(CfgMgrTest, getOptionDefs) {

+ 63 - 0
src/lib/dhcpsrv/tests/configuration_unittest.cc

@@ -0,0 +1,63 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <dhcpsrv/configuration.h>
+#include <gtest/gtest.h>
+
+using namespace isc::dhcp;
+
+// Those are the tests for Configuration storage. Right now they are minimal,
+// but the number is expected to grow significantly once we migrate more
+// parameters from CfgMgr storage to Configuration storage.
+
+namespace {
+
+// Check that by default there are no logging entries
+TEST(ConfigurationTest, basic) {
+    Configuration x;
+
+    EXPECT_TRUE(x.logging_info_.empty());
+}
+
+// Check that Configuration can store logging information.
+TEST(ConfigurationTest, loggingInfo) {
+
+    Configuration x;
+
+    LoggingInfo log1;
+    log1.name_ = "foo";
+    log1.severity_ = isc::log::WARN;
+    log1.debuglevel_ = 77;
+
+    LoggingDestination dest;
+    dest.output_ = "some-logfile.txt";
+    dest.maxver_ = 5;
+    dest.maxsize_ = 2097152;
+
+    log1.destinations_.push_back(dest);
+
+    x.logging_info_.push_back(log1);
+
+    EXPECT_EQ("foo", x.logging_info_[0].name_);
+    EXPECT_EQ(isc::log::WARN, x.logging_info_[0].severity_);
+    EXPECT_EQ(77, x.logging_info_[0].debuglevel_);
+
+    EXPECT_EQ("some-logfile.txt", x.logging_info_[0].destinations_[0].output_);
+    EXPECT_EQ(5, x.logging_info_[0].destinations_[0].maxver_);
+    EXPECT_EQ(2097152, x.logging_info_[0].destinations_[0].maxsize_);
+}
+
+} // end of anonymous namespace

+ 51 - 3
src/lib/dhcpsrv/tests/daemon_unittest.cc

@@ -15,18 +15,66 @@
 #include <config.h>
 #include <config.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 #include <dhcpsrv/daemon.h>
 #include <dhcpsrv/daemon.h>
+#include <dhcpsrv/logging.h>
+#include <cc/data.h>
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 
 
 using namespace isc;
 using namespace isc;
 using namespace isc::dhcp;
 using namespace isc::dhcp;
+using namespace isc::data;
 
 
 namespace {
 namespace {
 
 
-// Very simple test. Checks whether Daemon can be instantiated.
-TEST(DaemonTest, noop) {
-    EXPECT_NO_THROW(Daemon x);
+// Very simple test. Checks whether Daemon can be instantiated and its
+// default parameters are sane
+TEST(DaemonTest, constructor) {
+    EXPECT_NO_THROW(Daemon instance1);
+
+    // Check that the verbose mode is not set by default.
+    Daemon instance2;
+    EXPECT_FALSE(instance2.getVerbose());
+}
+
+// Checks that configureLogger method is behaving properly.
+// More dedicated tests are availablef for LogConfigParser class.
+// See logger_unittest.cc
+TEST(DaemonTest, parsingConsoleOutput) {
+
+    // Storage - parsed configuration will be stored here
+    ConfigurationPtr storage(new Configuration());
+
+    const char* config_txt =
+    "{ \"loggers\": ["
+    "    {"
+    "        \"name\": \"kea\","
+    "        \"output_options\": ["
+    "            {"
+    "                \"output\": \"stdout\""
+    "            }"
+    "        ],"
+    "        \"debuglevel\": 99,"
+    "        \"severity\": \"DEBUG\""
+    "    }"
+    "]}";
+    ConstElementPtr config = Element::fromJSON(config_txt);
+
+    // Spawn a daemon and tell it to configure logger
+    Daemon x;
+    EXPECT_NO_THROW(x.configureLogger(config, storage, false));
+
+    // The parsed configuration should be processed by the daemon and
+    // stored in configuration storage.
+    ASSERT_EQ(1, storage->logging_info_.size());
+
+    EXPECT_EQ("kea", storage->logging_info_[0].name_);
+    EXPECT_EQ(99, storage->logging_info_[0].debuglevel_);
+    EXPECT_EQ(isc::log::DEBUG, storage->logging_info_[0].severity_);
+
+    ASSERT_EQ(1, storage->logging_info_[0].destinations_.size());
+    EXPECT_EQ("stdout" , storage->logging_info_[0].destinations_[0].output_);
 }
 }
 
 
+
 // More tests will appear here as we develop Daemon class.
 // More tests will appear here as we develop Daemon class.
 
 
 };
 };

+ 224 - 0
src/lib/dhcpsrv/tests/logging_unittest.cc

@@ -0,0 +1,224 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <exceptions/exceptions.h>
+#include <cc/data.h>
+#include <dhcpsrv/logging.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::data;
+
+namespace {
+
+// Checks that contructor is able to process specified storage properly
+TEST(LoggingTest, constructor) {
+
+    ConfigurationPtr null_ptr;
+    EXPECT_THROW(LogConfigParser parser(null_ptr), BadValue);
+
+    ConfigurationPtr nonnull(new Configuration());
+
+    EXPECT_NO_THROW(LogConfigParser parser(nonnull));
+}
+
+// Checks if the LogConfigParser class is able to transform JSON structures
+// into Configuration usable by log4cplus. This test checks for output
+// configured to stdout on debug level.
+TEST(LoggingTest, parsingConsoleOutput) {
+
+    const char* config_txt =
+    "{ \"loggers\": ["
+    "    {"
+    "        \"name\": \"kea\","
+    "        \"output_options\": ["
+    "            {"
+    "                \"output\": \"stdout\""
+    "            }"
+    "        ],"
+    "        \"debuglevel\": 99,"
+    "        \"severity\": \"DEBUG\""
+    "    }"
+    "]}";
+
+    ConfigurationPtr storage(new Configuration());
+
+    LogConfigParser parser(storage);
+
+    // We need to parse properly formed JSON and then extract
+    // "loggers" element from it. For some reason fromJSON is
+    // throwing at opening square bracket
+    ConstElementPtr config = Element::fromJSON(config_txt);
+    config = config->get("loggers");
+
+    EXPECT_NO_THROW(parser.parseConfiguration(config));
+
+    ASSERT_EQ(1, storage->logging_info_.size());
+
+    EXPECT_EQ("kea", storage->logging_info_[0].name_);
+    EXPECT_EQ(99, storage->logging_info_[0].debuglevel_);
+    EXPECT_EQ(isc::log::DEBUG, storage->logging_info_[0].severity_);
+
+    ASSERT_EQ(1, storage->logging_info_[0].destinations_.size());
+    EXPECT_EQ("stdout" , storage->logging_info_[0].destinations_[0].output_);
+}
+
+// Checks if the LogConfigParser class is able to transform JSON structures
+// into Configuration usable by log4cplus. This test checks for output
+// configured to a file on INFO level.
+TEST(LoggingTest, parsingFile) {
+
+    const char* config_txt =
+    "{ \"loggers\": ["
+    "    {"
+    "        \"name\": \"kea\","
+    "        \"output_options\": ["
+    "            {"
+    "                \"output\": \"logfile.txt\""
+    "            }"
+    "        ],"
+    "        \"severity\": \"INFO\""
+    "    }"
+    "]}";
+
+    ConfigurationPtr storage(new Configuration());
+
+    LogConfigParser parser(storage);
+
+    // We need to parse properly formed JSON and then extract
+    // "loggers" element from it. For some reason fromJSON is
+    // throwing at opening square bracket
+    ConstElementPtr config = Element::fromJSON(config_txt);
+    config = config->get("loggers");
+
+    EXPECT_NO_THROW(parser.parseConfiguration(config));
+
+    ASSERT_EQ(1, storage->logging_info_.size());
+
+    EXPECT_EQ("kea", storage->logging_info_[0].name_);
+    EXPECT_EQ(0, storage->logging_info_[0].debuglevel_);
+    EXPECT_EQ(isc::log::INFO, storage->logging_info_[0].severity_);
+
+    ASSERT_EQ(1, storage->logging_info_[0].destinations_.size());
+    EXPECT_EQ("logfile.txt" , storage->logging_info_[0].destinations_[0].output_);
+}
+
+// Checks if the LogConfigParser class is able to transform data structures
+// into Configuration usable by log4cplus. This test checks that more than
+// one logger can be configured.
+TEST(LoggingTest, multipleLoggers) {
+
+    const char* config_txt =
+    "{ \"loggers\": ["
+    "    {"
+    "        \"name\": \"kea\","
+    "        \"output_options\": ["
+    "            {"
+    "                \"output\": \"logfile.txt\""
+    "            }"
+    "        ],"
+    "        \"severity\": \"INFO\""
+    "    },"
+    "    {"
+    "        \"name\": \"wombat\","
+    "        \"output_options\": ["
+    "            {"
+    "                \"output\": \"logfile2.txt\""
+    "            }"
+    "        ],"
+    "        \"severity\": \"DEBUG\","
+    "        \"debuglevel\": 99"
+    "    }"
+    "]}";
+
+    ConfigurationPtr storage(new Configuration());
+
+    LogConfigParser parser(storage);
+
+    // We need to parse properly formed JSON and then extract
+    // "loggers" element from it. For some reason fromJSON is
+    // throwing at opening square bracket
+    ConstElementPtr config = Element::fromJSON(config_txt);
+    config = config->get("loggers");
+
+    EXPECT_NO_THROW(parser.parseConfiguration(config));
+
+    ASSERT_EQ(2, storage->logging_info_.size());
+
+    EXPECT_EQ("kea", storage->logging_info_[0].name_);
+    EXPECT_EQ(0, storage->logging_info_[0].debuglevel_);
+    EXPECT_EQ(isc::log::INFO, storage->logging_info_[0].severity_);
+    ASSERT_EQ(1, storage->logging_info_[0].destinations_.size());
+    EXPECT_EQ("logfile.txt" , storage->logging_info_[0].destinations_[0].output_);
+
+    EXPECT_EQ("wombat", storage->logging_info_[1].name_);
+    EXPECT_EQ(99, storage->logging_info_[1].debuglevel_);
+    EXPECT_EQ(isc::log::DEBUG, storage->logging_info_[1].severity_);
+    ASSERT_EQ(1, storage->logging_info_[1].destinations_.size());
+    EXPECT_EQ("logfile2.txt" , storage->logging_info_[1].destinations_[0].output_);
+}
+
+// Checks if the LogConfigParser class is able to transform data structures
+// into Configuration usable by log4cplus. This test checks that more than
+// one logging destination can be configured.
+TEST(LoggingTest, multipleLoggingDestinations) {
+
+    const char* config_txt =
+    "{ \"loggers\": ["
+    "    {"
+    "        \"name\": \"kea\","
+    "        \"output_options\": ["
+    "            {"
+    "                \"output\": \"logfile.txt\""
+    "            },"
+    "            {"
+    "                \"output\": \"stdout\""
+    "            }"
+    "        ],"
+    "        \"severity\": \"INFO\""
+    "    }"
+    "]}";
+
+    ConfigurationPtr storage(new Configuration());
+
+    LogConfigParser parser(storage);
+
+    // We need to parse properly formed JSON and then extract
+    // "loggers" element from it. For some reason fromJSON is
+    // throwing at opening square bracket
+    ConstElementPtr config = Element::fromJSON(config_txt);
+    config = config->get("loggers");
+
+    EXPECT_NO_THROW(parser.parseConfiguration(config));
+
+    ASSERT_EQ(1, storage->logging_info_.size());
+
+    EXPECT_EQ("kea", storage->logging_info_[0].name_);
+    EXPECT_EQ(0, storage->logging_info_[0].debuglevel_);
+    EXPECT_EQ(isc::log::INFO, storage->logging_info_[0].severity_);
+    ASSERT_EQ(2, storage->logging_info_[0].destinations_.size());
+    EXPECT_EQ("logfile.txt" , storage->logging_info_[0].destinations_[0].output_);
+    EXPECT_EQ("stdout" , storage->logging_info_[0].destinations_[1].output_);
+}
+
+/// @todo There is no easy way to test applyConfiguration() and defaultLogging().
+/// To test them, it would require instrumenting log4cplus to actually fake
+/// the logging set up. Alternatively, we could develop set of test suites
+/// that check each logging destination spearately (e.g. configure log file, then
+/// check if the file is indeed created or configure stdout destination, then
+/// swap console file descriptors and check that messages are really logged.
+
+};

+ 1 - 1
src/lib/log/logger_manager.cc

@@ -51,7 +51,7 @@ int& initDebugLevel() {
 }
 }
 
 
 std::string& initRootName() {
 std::string& initRootName() {
-    static std::string root("kea");
+    static std::string root(isc::log::getDefaultRootLoggerName());
     return (root);
     return (root);
 }
 }
 
 

+ 5 - 0
src/lib/log/logger_name.cc

@@ -40,6 +40,11 @@ const std::string& getRootLoggerName() {
     return (getRootLoggerNameInternal());
     return (getRootLoggerNameInternal());
 }
 }
 
 
+const std::string& getDefaultRootLoggerName() {
+    static std::string root_name("kea");
+    return (root_name);
+}
+
 std::string expandLoggerName(const std::string& name) {
 std::string expandLoggerName(const std::string& name) {
 
 
     // Are we the root logger, or does the logger name start with
     // Are we the root logger, or does the logger name start with

+ 6 - 0
src/lib/log/logger_name.h

@@ -40,6 +40,12 @@ void setRootLoggerName(const std::string& name);
 /// \return Name of the root logger.
 /// \return Name of the root logger.
 const std::string& getRootLoggerName();
 const std::string& getRootLoggerName();
 
 
+
+/// @brief Returns the default ('kea') root logger name
+///
+/// @return The default name of root logger.
+const std::string& getDefaultRootLoggerName();
+
 /// \brief Expand logger name
 /// \brief Expand logger name
 ///
 ///
 /// Given a logger name, returns the fully-expanded logger name.  If the name
 /// Given a logger name, returns the fully-expanded logger name.  If the name

+ 2 - 1
src/lib/log/logger_unittest_support.cc

@@ -154,7 +154,8 @@ void initLogger(isc::log::Severity severity, int dbglevel) {
     const char* DEFAULT_ROOT = "kea";
     const char* DEFAULT_ROOT = "kea";
     const char* root = getenv("KEA_LOGGER_ROOT");
     const char* root = getenv("KEA_LOGGER_ROOT");
     if (! root) {
     if (! root) {
-        root = DEFAULT_ROOT;
+        // If not present, the name is "kea".
+        root = isc::log::getDefaultRootLoggerName().c_str();
     }
     }
 
 
     // Set the local message file
     // Set the local message file

+ 5 - 0
src/lib/log/tests/logger_name_unittest.cc

@@ -75,3 +75,8 @@ TEST_F(LoggerNameTest, ExpandLoggerName) {
     EXPECT_EQ(FULL_NAME, expandLoggerName(NAME));
     EXPECT_EQ(FULL_NAME, expandLoggerName(NAME));
     EXPECT_EQ(FULL_NAME, expandLoggerName(FULL_NAME));
     EXPECT_EQ(FULL_NAME, expandLoggerName(FULL_NAME));
 }
 }
+
+// Checks that the default logger name is returned properly.
+TEST_F(LoggerNameTest, default) {
+    EXPECT_EQ("kea", getDefaultRootLoggerName());
+}

+ 38 - 0
tools/path_replacer.sh.in

@@ -0,0 +1,38 @@
+#!/bin/sh
+# Copyright (C) 2014  Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+#
+# This script replaces @prefix@, @localstatedir@ and other automake/autoconf
+# variables with their actual content.
+#
+# Invocation:
+#
+# ./path_replacer.sh input-file.in output-file
+#
+# This script is initially used to generate configuration files, but it is
+# generic and can be used to generate any text files.
+#
+
+prefix=@prefix@
+sysconfdir=@sysconfdir@
+localstatedir=@localstatedir@
+
+echo "Replacing \@prefix\@ with ${prefix}"
+echo "Replacing \@sysconfdir\@ with ${sysconfdir}"
+echo "Replacing \@localstatedir\@ with ${localstatedir}"
+
+echo "Input file: $1"
+echo "Output file: $2"
+
+sed -e "s+\@localstatedir\@+${localstatedir}+g; s+\@prefix@+${prefix}+g; s+\@sysconfdir@+${sysconfdir}+g" $1 > $2