diff --git a/Changelog.md b/Changelog.md
new file mode 100644
--- /dev/null
+++ b/Changelog.md
@@ -0,0 +1,3 @@
+# 1.0.0
+
+* Initial version, supporting structured logging to the journal.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2014, Oliver Charles
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Oliver Charles nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/libsystemd-journal.cabal b/libsystemd-journal.cabal
new file mode 100644
--- /dev/null
+++ b/libsystemd-journal.cabal
@@ -0,0 +1,23 @@
+name:                libsystemd-journal
+version:             1.0.0
+synopsis:            Haskell bindings to libsystemd-journal
+homepage:            http://github.com/ocharles/libsystemd-journal
+license:             BSD3
+license-file:        LICENSE
+author:              Oliver Charles
+maintainer:          ollie@ocharles.org.uk
+copyright: Oliver Charles (c) 2014
+category:            System
+build-type:          Simple
+cabal-version:       >=1.10
+
+extra-source-files:
+  Changelog.md
+
+library
+  exposed-modules:     Systemd.Journal
+  build-depends:       base >=4.6 && <4.7, text >= 0.1 && < 1.2, unix-bytestring >= 0.3.2 && < 0.4, vector >= 0.4 && < 0.11, uuid, unordered-containers >= 0.1 && < 0.3, hashable >= 1.1.2.5, hsyslog
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+  extra-libraries: systemd-journal
+  ghc-options: -Wall
diff --git a/src/Systemd/Journal.hs b/src/Systemd/Journal.hs
new file mode 100644
--- /dev/null
+++ b/src/Systemd/Journal.hs
@@ -0,0 +1,161 @@
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE OverloadedStrings #-}
+module Systemd.Journal
+    ( -- * Logging
+      logMessage
+    , logMessageWith
+    , logProperties
+
+      -- * Log Properties
+    , LogProperties
+
+      -- ** Standard systemd log properties
+    , message
+    , messageId
+    , priority
+    , Syslog.Priority(..)
+    , codeFile
+    , codeLine
+    , codeFunc
+    , errno
+    , syslogFacility
+    , syslogIdentifier
+    , syslogPid
+
+      -- ** Custom Properties
+    , LogKey
+    , mkLogKey
+    ) where
+
+import Control.Monad (void)
+import Data.Char (toUpper)
+import Data.Data (Data)
+import Data.Hashable (Hashable)
+import Data.Monoid (Monoid, mappend)
+import Data.String (IsString (..))
+import Data.Typeable (Typeable)
+import Foreign (Ptr, throwIfNeg)
+import System.Posix.Types (CPid(..))
+
+import qualified Data.HashMap.Strict as HashMap
+import qualified Data.Text as Text
+import qualified Data.Text.Encoding as Text
+import qualified Data.UUID as UUID
+import qualified Data.Vector.Storable as V
+import qualified System.Posix.Syslog as Syslog
+import qualified System.Posix.Types.Iovec as Iovec
+
+--------------------------------------------------------------------------------
+foreign import ccall "sd_journal_sendv"
+  sdJournalSendV :: Ptr Iovec.CIovec -> Int -> IO Int
+
+--------------------------------------------------------------------------------
+newtype LogKey = LogKey Text.Text
+  deriving (Eq, Data, Hashable, Ord, Read, Show, Typeable, Monoid)
+
+instance IsString LogKey where
+  fromString = LogKey . Text.pack . map toUpper
+
+--------------------------------------------------------------------------------
+-- | Construct a 'LogKey' by converting to uppercase, as required by the
+-- journal.
+mkLogKey :: Text.Text -> LogKey
+mkLogKey = LogKey . Text.toUpper
+
+--------------------------------------------------------------------------------
+-- | A structured object of properties to log. You generally don't construct
+-- this yourself, but you use the monoid instance and smart constructors below.
+-- For example,
+--
+-- > logProperties (message "Oh god, it burns!" <> priority Emergency)
+type LogProperties = HashMap.HashMap LogKey Text.Text
+
+--------------------------------------------------------------------------------
+-- | The human readable message string for this entry. This is supposed to be
+-- the primary text shown to the user. It is usually not translated (but might be
+-- in some cases), and is not supposed to be parsed for meta data.
+message :: Text.Text -> LogProperties
+message = HashMap.singleton (LogKey "MESSAGE")
+
+--------------------------------------------------------------------------------
+-- | A 128bit message identifier ID for recognizing certain message types, if
+-- this is desirable. Developers can generate a new ID for this purpose with
+-- @journalctl --new-id@.
+messageId :: UUID.UUID -> LogProperties
+messageId =
+  HashMap.singleton (LogKey "MESSAGE_ID") . Text.pack . UUID.toString
+
+--------------------------------------------------------------------------------
+-- | A priority value compatible with syslog's priority concept.
+priority :: Syslog.Priority -> LogProperties
+priority =
+  HashMap.singleton (LogKey "PRIORITY") . Text.pack . show . fromEnum
+
+--------------------------------------------------------------------------------
+-- | The source code file generating this message.
+codeFile :: FilePath -> LogProperties
+codeFile =
+  HashMap.singleton (LogKey "CODE_FILE") . Text.pack
+
+--------------------------------------------------------------------------------
+-- | The source code line number generating this message.
+codeLine :: Int -> LogProperties
+codeLine = HashMap.singleton (LogKey "CODE_LINE") . Text.pack . show
+
+--------------------------------------------------------------------------------
+-- | The source code function name generating this message.
+codeFunc :: Text.Text -> LogProperties
+codeFunc = HashMap.singleton (LogKey "CODE_FUNC")
+
+--------------------------------------------------------------------------------
+-- | The low-level Unix error number causing this entry, if any. Contains the
+-- numeric value of @errno(3)@.
+errno :: Int -> LogProperties
+errno = HashMap.singleton (LogKey "ERRNO") . Text.pack . show
+
+--------------------------------------------------------------------------------
+-- | Syslog compatibility field.
+syslogFacility :: Syslog.Facility -> LogProperties
+syslogFacility =
+  HashMap.singleton (LogKey "SYSLOG_FACILITY") . Text.pack . show . fromEnum
+
+--------------------------------------------------------------------------------
+-- | Syslog compatibility field.
+syslogIdentifier :: Text.Text -> LogProperties
+syslogIdentifier =
+  HashMap.singleton (LogKey "SYSLOG_IDENTIFIER")
+
+--------------------------------------------------------------------------------
+-- | Syslog compatibility field.
+syslogPid :: CPid -> LogProperties
+syslogPid (CPid pid) =
+  HashMap.singleton (LogKey "SYSLOG_PID") (Text.pack $ show pid)
+
+--------------------------------------------------------------------------------
+-- | Log a message to the systemd journal.
+logMessage :: Text.Text -> IO ()
+logMessage = logProperties . message
+
+--------------------------------------------------------------------------------
+-- | Log a message and supply extra metadata. All metadata keys will be
+-- converted to uppercase for you.
+--
+-- Note: The 'Message' meta-data property will be replaced with the first
+-- parameter to this function. If you don't want this, use 'logMap'
+logMessageWith :: Text.Text -> LogProperties -> IO ()
+logMessageWith text meta = logProperties $ mappend meta $ message text
+
+--------------------------------------------------------------------------------
+logProperties :: LogProperties -> IO ()
+logProperties meta = void $
+  throwIfNeg (("sd_journal_send returned :" ++) . show) $
+  go id 0 (HashMap.toList meta)
+
+  where
+  go f n [] = V.unsafeWith (V.fromList (f [])) $ \iovecs ->
+    sdJournalSendV iovecs n
+
+  go f n ((LogKey k, v) : xs) =
+    Iovec.unsafeUseAsCIovec (Text.encodeUtf8 (Text.intercalate "=" [k, v])) $
+      \messageIovec -> go (f . (++ [messageIovec])) (n + 1) xs
